JFinal使用技巧-数据库增加乐观锁扩展

如题,以前在社区回答过解决方案,今天在群里又看到有人问了。嗯,抗过疫情,生活继续分享~ 不多说,上 石马 ~

使用 ActiveRecordPlugin 扩展:
建一个类:

import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.activerecord.Table;
import com.jfinal.plugin.activerecord.dialect.MysqlDialect;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * MysqlDialectByVersion扩展
 * 注意 version 字段, 本扩展会自动 + 1 ,不需要在前端等其他地方版本号加1
 * @author 杜福忠
 */
public class MysqlDialectByVersion extends MysqlDialect {
    private String versionColName = "version";

    public MysqlDialectByVersion() {
    }

    public MysqlDialectByVersion(String versionColName) {
        this.versionColName = versionColName;
    }

    public void forModelUpdate(Table table, Map<String, Object> attrs, Set<String> modifyFlag, StringBuilder sql, List<Object> paras) {
        Integer version = null;
        if (table.hasColumnLabel(versionColName)){
            version = putVersion(attrs);
            modifyFlag.add(versionColName);
        }
        super.forModelUpdate(table, attrs, modifyFlag, sql, paras);

        appendSqlByVersion(sql, paras, version);
    }

    /**
     * Record 的 version 字段需要在业务中判断对象是否缺失版本号字段,只有对象里面存在版本号字段后才会触发拼接and条件
     */
    public void forDbUpdate(String tableName, String[] pKeys, Object[] ids, Record record, StringBuilder sql, List<Object> paras) {
        Integer version = null;
        Map<String, Object> attrs = record.getColumns();
        if (attrs.containsKey(versionColName)) {
            version = putVersion(attrs);
        }
        super.forDbUpdate(tableName, pKeys, ids, record, sql, paras);

        appendSqlByVersion(sql, paras, version);
    }

    private void appendSqlByVersion(StringBuilder sql, List<Object> paras, Integer version) {
        if (Objects.nonNull(version)) {
            sql.append(" and `").append(versionColName).append("` < ?");
            paras.add(version);
        }
    }

    private Integer putVersion(Map<String, Object> attrs) {
        Object v = attrs.get(versionColName);
        Objects.requireNonNull(v, versionColName + "字段,不能为null");
        Integer version = toInteger(v) + 1;
        attrs.put(versionColName, version);
        return version;
    }

    private Integer toInteger(Object v) {
        return v instanceof Integer ? (Integer) v :  Integer.valueOf(v.toString());
    }

}

OK,我就写了MySQL的,其他数据库,应该可以模仿,配置使用:

ActiveRecordPlugin arp = new ActiveRecordPlugin(...);
arp.setDialect(new MysqlDialectByVersion());


测试:

public static void main(String[] args) {
    ConfigActiveRecordKit.run();//这里是自己封装的数据库启动和Model映射方便业务测试
    Account account = Account.dao.findById(2);
    account.set("remark", System.currentTimeMillis());
    //debug断点打在这个位置,然后数据库去手动修改版本号,观察效果
    boolean update = account.update();
    System.out.println(update);
    Record a = new Record();
    a.set("id", 2);
    //a.set("version", account.get("version"));
    a.set("remark", "Record");
    if (! a.getColumns().containsKey("version")){
        throw new NullPointerException("老铁!没有版本号字段啊");
    }
    boolean ret = Db.update("account", a);
    System.out.println(ret);
}


说明文档:
image.pngimage.png
image.png
原理:利用SQL的update语句,where后面追加 and 条件去实现更新是否成功的判断。
 
当有Model时,数据库表里面需要有 version 字段int类型,会自动识别,注意字段设置默认值为0。
Record时,需要在业务代码里面判断是否存在version 字段。

判断成功方式:update方法 返回true为成功。
注意并不绝对是因为乐观锁导致的更新不成功,可能是主键值不存在了等情况,and条件嘛。


 好了,有参考价值就点个赞呗~ 有特殊业务自行改造吧~

评论区

JFinal

2022-02-09 18:06

这个用法比较高级,收藏一波

山东小木

2022-02-10 11:51

老杜 给我们讲讲 mysql 十万级到百万级 数据量 单表分页查询优化吧 分享一下经验

杜福忠

2022-02-10 15:18

@山东小木 o(╥﹏╥)o 没啥经验,设计表业务的时候尽量避免大表出现,做好索引。。。如果CPU还飙升,就根据查询条件缓存总行数,别总去count*,手动find分页就行。。。如果还飙升,充钱就行。。。我一般优先充钱解决问题。。。
image.png
image.png

山东小木

2022-02-11 11:26

@杜福忠 jfinal内置的paginate 底层是通过select count(*) 去查询总数量的 直接find数据没问题 但是反应给前端的数据 需要带着分页信息里的 符合条件的总数是多少 这次查询的是第几页 每页多少条数据

杜福忠

2022-02-11 11:38

@山东小木 是的,我遇到一些慢SQL有因为 select count(*) 导致CPU暴涨的,在索引已经加过的情况下,就是去掉paginate,手动find查询再二次包装返回对象了。
应该是可以扩展paginate方法的,但毕竟我没有那么多慢SQL,业务哪个点遇到了就单处理了,没做封装。确实可以瞅瞅paginate方法看咋简单的扩展一下

happyboy

2022-02-11 14:14

@JFinal 我之前就想提这个功能,分页的时候,波总是否可以提供一个自行计算count总数的接口,已满足少数场景下需要优化查询效率的场景。

北流家园网

2022-02-14 09:00

杜福忠 的技术贴就是有质量,必须收藏,不管懂不懂,先收藏再说

zeroabc

2022-02-18 15:45

神奇用法get