SpringBoot入门学习笔记-18-MybatisPlus乐观锁与Update方法封装

简单的说,乐观说就是一个修改记录版本记录。每次提交修改时都要提交这个版本号,数据库里用来复核对不行。相当于是这样:

update tb set count =new,version = version+1 count where version= version

每次更新时,version都会更新,比如自动=1.这样下次再更新时,由于version已经变了就不会被更新了。这样可以确认每一次的更新都能在对的版本上,如果版本不对,就需要重新获取数据。

mybatisPlus 的自带了乐观锁的功能,一般使用int 类型的version当版本号,或者用updateTime来当版本号。

两者适用的场景不同,各有优劣。下面我以updatedAt当锁来讲解如何配置。

1、配置类注入mybatisPlus乐观锁的拦截器

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}

2、定义一个自动填充行为,用来强制将当前AutoFillFieldValueConfig。

在updateFill中将updatedAt当前锁,强制更新当前时间。

package com.luo.comm.config.mybatisPlus;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.luo.comm.entity.User;
import com.luo.comm.utils.ThreadLocal.ThreadlUser;
import com.luo.comm.vo.BusinessException;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 自动填充字段值的配置
 * 插入时,保存创建时间和创建人,以及更新时间和更新人。
 * 更新时,保存更新时间和更新人。
 * 其中,创建人,更新人都是从ThreadlUser获取的当前线程用户信息
 */

@Component
public class AutoFillFieldValueConfig implements MetaObjectHandler {

    private static final String createdBy = "createdBy";
    private static final String updatedBy = "updatedBy";
    private static final String createdAt = "createdAt";
    private static final String updatedAt = "updatedAt";


    @Override
    public void insertFill(MetaObject metaObject) {

        User loginUser = getLoginUser();
        this.strictInsertFill(metaObject, createdAt, Date.class, new Date());
        this.strictInsertFill(metaObject, createdBy, String.class, loginUser.getUserName());
        this.strictInsertFill(metaObject, updatedAt, Date.class, new Date());
        this.strictInsertFill(metaObject, updatedBy, String.class, loginUser.getUserName());

    }

    @Override
    public void updateFill(MetaObject metaObject) {
        User loginUser = getLoginUser();
        this.strictInsertFill(metaObject, updatedAt, Date.class, new Date());
        this.strictInsertFill(metaObject, updatedBy, String.class, loginUser.getUserName());
    }


    private User getLoginUser (){
        /** 获取当前线程的用户信息,检查是否登陆 */
        User loginUser = ThreadlUser.read();
        System.out.println("当前线程1:"+ Thread.currentThread().getId() +"user:" +loginUser);
        if(StringUtils.isEmpty(loginUser.getUserName()) || StringUtils.isEmpty(loginUser.getRoleId())){
            throw BusinessException.error ("用户未登陆或不存在");
        }
        return loginUser;
    }


}

3、在实体中,通过@version注解标识锁字段

添加了@Version以后,mybatisPlus会自动识别。

    /** 更新时间*/
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @JsonInclude(JsonInclude.Include.NON_NULL) // 当为空时不转换JSON输出。这样前端就不会返回null.
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @Version
    private Date updatedAt;

4、在通用update方法中,beforeUpdateComm中调用一个通用处理方法,用来确保所以有update方法都会得到执行beforeUpdateCommHandle。

    public final int update(T entity){
        // 将原变量的属性复制一份到新实体类。后面都用新实体类处理。
//        T newEntity =entity;

        beforeUpdate(entity);
        beforeUpdateComm(entity);
        int i = baseMapper.updateById(entity);
        return i;
    };

    protected void beforeUpdate(T entity){
    };

    private void beforeUpdateComm(T entity){
        // 调用保存方法前处理过程,进行一些通用字段的处理或通用条件的处理
        MpUtils.beforeUpdateCommHandle(entity);
    }
package com.luo.comm.services.mp;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.luo.comm.utils.mp.MpUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 通用基类,定义了通用的方法.Service中都是封装的方法,如果要用原生的方法,请直接使用UserMapper的方法。
 * public方法是最终对外暴露的方法,不建议子类重写特意加了final关键字禁止子类重写。
 * save方法,内部调用了beforeSave、beforeSaveComm、afterSaveComm、afterSave。Comm结尾的是公共的,不能被子类重写。
 * 同理,增删改查方法内部都会调用一些处理过程。Comm结尾的是公共的方法,不能被子类重写。
 *
 * protected 方法 能被子类继承和重写。
 * private 方法 仅能在本类中调用,不能被子类继承,也不能被子类重写。
 *
 * @author bill
 * @date 2023-07-07
 * */
@Component
public  class BaseService<T> {

    @Resource
    BaseMapper<T> baseMapper;

    public final int save(T entity){
        // 将原变量的属性复制一份到新实体类。后面都用新实体类处理。
//        T newEntity =entity;

        beforeSave(entity);
        beforeSaveComm(entity);
        int insert = baseMapper.insert(entity);

        return insert;
    };

    protected void beforeSave(T entity){
    };

    private void beforeSaveComm(T entity){
        // 调用保存方法前处理过程,进行一些通用字段的处理或通用条件的处理
         MpUtils.beforeSaveCommHandle(entity);
    }


    /**
     * 这是一个单条修改的方法。
     * 采用乐观锁更新机制,锁不是version,是updateAt.精确到豪米。所以每次更新时都要携带updateAt字段
     *
     * */
    public final int update(T entity){
        // 将原变量的属性复制一份到新实体类。后面都用新实体类处理。
//        T newEntity =entity;

        beforeUpdate(entity);
        beforeUpdateComm(entity);
        int i = baseMapper.updateById(entity);
        return i;
    };

    protected void beforeUpdate(T entity){
    };

    private void beforeUpdateComm(T entity){
        // 调用保存方法前处理过程,进行一些通用字段的处理或通用条件的处理
        MpUtils.beforeUpdateCommHandle(entity);
    }

    public final long count(QueryWrapper queryWrapper){
        return baseMapper.selectCount(queryWrapper);
    };
}

5、实现类中,只需要继承即可。

public class UserService extends BaseService<User> {}

6、beforeUpdateCommHandle中进行处理,检查一定要有updatedAt字段。

package com.luo.comm.utils.mp;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.luo.comm.vo.BusinessException;
import com.luo.comm.vo.MyResEnum;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;

import java.util.Arrays;
import java.util.List;

public class MpUtils {

    /**
     * 保存前的预处理函数,像删除id\isDelete字段等
     * @param object 入参是一个通用实体类,转换成jsonobject进行处理*/

    private static final String id = "id";
    private static final String createdBy = "createdBy";
    private static final String updatedBy = "updatedBy";
    private static final String createdAt = "createdAt";
    private static final String updatedAt = "updatedAt";
    private static final String deletedBy = "deletedBy";
    private static final String deletedAt = "deletedAt";

    public static Object beforeSaveCommHandle(Object object)   {

        Object obj = object;

        /** 将baseEntity的属性补充到当前类,供后面使用 */
        Class<?> cls = obj.getClass();
        JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(obj));

        /** 处理需要删除的字段。这些是不能指定添加的 */
        // removeList列表中字段都会被删除处理。约定这些是特殊字段,不能由用户填写
        List<String> removeList = Arrays.asList(id,createdBy,updatedBy,createdAt,updatedAt,deletedBy,deletedAt);
        for (String item: removeList ) {
            if(jsonObject.containsKey(item)){
                jsonObject.remove(item);
            }
        }
        Object newObj = jsonObject.toJavaObject(cls);

        // 处理结束后,将新实体类的属性再复制回原实体类,以便调用者可以直接用实体类获取新值。
        BeanUtils.copyProperties(newObj,object);

        /**是否返回object并不影响调用者通过object获取新值,这里也可以返回int或bool值*/
        return object;
    }

    /**
     * 保存前的预处理函数,删除createdBy,createdAt,deletedBy,deletedAt字段等
     * @param object 入参是一个通用实体类,转换成jsonobject进行处理*/

    public static Object beforeUpdateCommHandle(Object object) {

        Object obj = object;
        /** 将baseEntity的属性补充到当前类,供后面使用 */
        Class<?> cls = obj.getClass();
        JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(obj));

        /**检查是否存在id字段。不存在抛异常*/
        if(!jsonObject.containsKey(id) || ObjectUtils.isEmpty(jsonObject.get(id))){
            throw new BusinessException(MyResEnum.ID_EMPTY); // update方法调用的其实是updatedById,所以必须提供id。
        }

        /**检查是否存在乐观锁updateAt字段。不存在抛异常*/
        if(!jsonObject.containsKey(updatedAt) || ObjectUtils.isEmpty(jsonObject.get(updatedAt))){
            throw BusinessException.error("更新时必须提供updatedAt字段");
        }


        /** 处理需要删除的字段。这些是不能指定的 */
        // removeList列表中字段都会被删除处理。
        List<String> removeList = Arrays.asList(createdBy,createdAt,deletedBy,deletedAt);
        for (String item: removeList ) {
            if(jsonObject.containsKey(item)){
                jsonObject.remove(item);
            }
        }

        /** 删除公用字段外,除id和updatedAt字段以外,还需要包括至少一个字段,否则无更新内容*/
        if(jsonObject.keySet().stream().count()<3){
            throw new BusinessException(MyResEnum.UPDATE_NO_FIELDS);
        }

        Object newObj = jsonObject.toJavaObject(cls);

        // 处理结束后,将新实体类的属性再复制回原实体类,以便调用者可以直接用实体类获取新值。
        BeanUtils.copyProperties(newObj,object);

        /**是否返回object并不影响调用者通过object获取新值,这里也可以返回int或bool值*/

        return object;
    }

}

7、controller调用示例

    @PostMapping("/update")
    public ResultsObj update(@RequestBody User user)  {
        int i = userService.update(user);
        ResultsObj resultsObj =i>0 ?new ResultsObj(user):new ResultsObj(MyResEnum.UPDATE_FAIL);
        return resultsObj;
    }

8、postman调用示例

 

 如果不提供正确的updatedAt字段,都会更新失败。

下面提供正确的,返回成功:

9、本项目gitee代码仓地址

截止到目前,已经集成了mybatisPlus并进行了通用的save\update方法的封装。

为便于直接使用,本项目的代码分享到gitee了,可直接下载。

laoluo: Springboot、SpringCloud项目 

本项目代码位置于上述代码仓项目中services-backend项目。

本项目代码后面也会持续更新,并添加更多项目。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值