【MyBatis-Plus】 使用乐观锁时,自动更新FieldFill.INSERT_UPDATE失效

本文探讨了如何在SpringBoot应用中使用MyBatis-Plus实现乐观锁,并解决update_time字段自动填充问题。通过添加版本号字段、配置插件及自定义元对象处理器,确保在更新操作时正确处理并发和时间戳更新。
摘要由CSDN通过智能技术生成

目录

 【发现问题】

 【验证问题】

 【分析问题】

 【解决问题】


版本: 

SpringBoot     :<version>2.5.2</version>

MyBatis-Plus   : <version>3.4.3.1</version>


乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。

实现两种方式
  1. CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
  2. 版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会 +1。当线程 A 要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

版本号控制实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

 【发现问题】

1、数据库表中添加字段

2、在实体类字段上添加 @version 注解

  @Version
    private Integer version;

3、配置插件

package com.angus.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author angus
 * @date 2021-07-05 0:04
 */
@Configuration
@MapperScan("com.angus.dao")
public class MyBatisConfig {
    /**
     * 新版 乐观锁插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

 4、测试:

  //测试乐观锁(成功修改)
    @Test
    void testOptimisticLocker(){
        // 1、查询数据
        Guardian guardian = guardianMapper.selectById(1411728023050883073L);
        //修改数据
        guardian.setName("小柠檬1");
        //执行修改
        int i = guardianMapper.updateById(guardian);
       log.info(guardian.toString());

    }

 结果:数据库直接查询结果

 version  修改为2:;乐观锁 成功

但是发现问题,关于update_time 字段值没有因为修改而自动填充功能生效 

控制台日志输出

JDBC Connection [HikariProxyConnection@1387174267 wrapping com.mysql.cj.jdbc.ConnectionImpl@f557c37] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,version,is_delete,create_time,update_time FROM guardian WHERE id=?
==> Parameters: 1(Long)
<==    Columns: id, name, age, email, version, is_delete, create_time, update_time
<==        Row: 1, Jone, 18, test1@baomidou.com, 1, 0, 2021-07-03 20:05:52, 2021-07-03 20:05:52
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4e83a98]




Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6436e181] was not registered for synchronization because synchronization is not active
2021-07-05 02:17:14.250  INFO 40980 --- [           main] com.angus.handler.MyMetaObjectHandler    : start update fill ....
JDBC Connection [HikariProxyConnection@299684908 wrapping com.mysql.cj.jdbc.ConnectionImpl@f557c37] will not be managed by Spring
==>  Preparing: UPDATE guardian SET name=?, age=?, email=?, version=?, is_delete=?, create_time=?, update_time=? WHERE id=? AND version=?
==> Parameters: 小柠檬(String), 18(Integer), test1@baomidou.com(String), 2(Integer), 0(Integer), 2021-07-03 20:05:52.0(Timestamp), 2021-07-03 20:05:52.0(Timestamp), 1(Long), 1(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6436e181]

 【验证问题】

查看自动填充功能代码

1、实体类  添加注解

    @TableField(fill = FieldFill.INSERT )
    private Date createTime;


    @TableField(fill =FieldFill.INSERT_UPDATE)
    private  Date updateTime;

2、 自定义实现类 MyMetaObjectHandler;实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler

package com.angus.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.handlers.StrictFill;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Date;

/**
 * @author angus
 * @date 2021-07-04 18:05
 */
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
//        default <T, E extends T> MetaObjectHandler strictInsertFill(MetaObject metaObject, String fieldName, Supplier<E> fieldVal, Class<T> fieldType) {
//            return this.strictInsertFill(this.findTableInfo(metaObject), metaObject, Collections.singletonList(StrictFill.of(fieldName, fieldVal, fieldType)));
//        }

       // this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        // 或者
        this.strictInsertFill(metaObject, "createTime", () ->new Date(), Date.class); // 起始版本 3.3.3(推荐)
        this.strictInsertFill(metaObject, "updateTime", () ->new Date(), Date.class); // 起始版本 3.3.3(推荐)
        // 或者
      //  this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)

//        this.setFieldValByName("createTime",new Date(),metaObject);  //可以使用
//        this.setFieldValByName("updateTime",new Date(),metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", () ->new Date() , Date.class);

        //this.setFieldValByName("updateTime",new Date(),metaObject);
    }


}

 测试:

添加新数据

  @Test
    void  testInsert(){
        Guardian guardian=new Guardian();
        guardian.setName("小柠檬"+ LocalDate.now());
        int insert = guardianMapper.insert(guardian);
       log.info(guardian.toString());
    }

 结果:成功

 新增数据 create_time,update_time 字段 自动填充成功

 修改数据

 @Test
    void testUpdate(){
        Guardian guardian = new Guardian();
        guardian.setId(1411754209713934338L);
        guardian.setName("爱干饭的贾觉非");
        guardian.setEmail("737093684@qq.com");
        int i = guardianMapper.updateById(guardian);
    }

结果:成功

update_time 字段修改为新时间;

由于直接根据Id修改的,没有从数据库查询获取,version没有+1

乐观锁

 //测试乐观锁(成功修改)
    @Test
    void testOptimisticLocker(){
        // 1、查询数据
        Guardian guardian = guardianMapper.selectById(1411754209713934338L);
        //修改数据
        guardian.setName("小柠檬");
        //执行修改
        int i = guardianMapper.updateById(guardian);
       log.info(guardian.toString());

    }

结果: 问题出现


 【分析 问题】

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4e83a98] was not registered for synchronization because synchronization is not active
2021-07-05 02:40:06.985  INFO 42488 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-07-05 02:40:10.110  INFO 42488 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1387174267 wrapping com.mysql.cj.jdbc.ConnectionImpl@f557c37] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,version,is_delete,create_time,update_time FROM guardian WHERE id=?
==> Parameters: 1411754209713934338(Long)
<==    Columns: id, name, age, email, version, is_delete, create_time, update_time
<==        Row: 1411754209713934338, 爱干饭的贾觉非, null, 737093684@qq.com, 1, 0, 2021-07-05 02:30:07, 2021-07-05 02:35:12
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4e83a98]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6436e181] was not registered for synchronization because synchronization is not active
2021-07-05 02:40:11.302  INFO 42488 --- [           main] com.angus.handler.MyMetaObjectHandler    : start update fill ....
JDBC Connection [HikariProxyConnection@299684908 wrapping com.mysql.cj.jdbc.ConnectionImpl@f557c37] will not be managed by Spring
==>  Preparing: UPDATE guardian SET name=?, email=?, version=?, is_delete=?, create_time=?, update_time=? WHERE id=? AND version=?
==> Parameters: 小柠檬(String), 737093684@qq.com(String), 2(Integer), 0(Integer), 2021-07-05 02:30:07.0(Timestamp), 2021-07-05 02:35:12.0(Timestamp), 1411754209713934338(Long), 1(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6436e181]
2021-07-05 02:40:11.658  INFO 42488 --- [           main] com.angus.MybatisPlusApplicationTests    : Guardian(id=1411754209713934338, name=小柠檬, age=null, email=737093684@qq.com, version=2, isDelete=0, createTime=Mon Jul 05 02:30:07 CST 2021, updateTime=Mon Jul 05 02:35:12 CST 2021)

根据控制台日志 分析

从数据库中查询回来的数据,即便我们修改了updateTime字段之外的值,然后提交修改,如果updateTime字段原先就有数据,那么此时数据不变,也就是自动注入失效了。


【解决】

1、实体类添加注解属性 ,update=“now()”

     @TableField(fill =FieldFill.INSERT_UPDATE,update = "now()")
     private  Date updateTime;
   
 @Test
    void testOptimisticLocker(){
        // 1、查询数据
        Guardian guardian = guardianMapper.selectById(1411754209713934338L);
        //修改数据
        guardian.setName("铁锤霉霉");
        //执行修改
        int i = guardianMapper.updateById(guardian);
       log.info(guardian.toString());

    }

结果:成功  version +1;update_time 更新为当前时间

查看日志

JDBC Connection [HikariProxyConnection@2034723406 wrapping com.mysql.cj.jdbc.ConnectionImpl@52ae997b] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,version,is_delete,create_time,update_time FROM guardian WHERE id=?
==> Parameters: 1411754209713934338(Long)
<==    Columns: id, name, age, email, version, is_delete, create_time, update_time
<==        Row: 1411754209713934338, 铁锤霉霉, null, 737093684@qq.com, 2, 0, 2021-07-05 02:30:07, 2021-07-05 02:35:12
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5377414a]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e700eba] was not registered for synchronization because synchronization is not active
2021-07-05 02:51:58.292  INFO 42140 --- [           main] com.angus.handler.MyMetaObjectHandler    : start update fill ....
JDBC Connection [HikariProxyConnection@1308527415 wrapping com.mysql.cj.jdbc.ConnectionImpl@52ae997b] will not be managed by Spring
==>  Preparing: UPDATE guardian SET name=?, email=?, version=?, is_delete=?, create_time=?, update_time=now() WHERE id=? AND version=?
==> Parameters: 铁锤霉霉(String), 737093684@qq.com(String), 3(Integer), 0(Integer), 2021-07-05 02:30:07.0(Timestamp), 1411754209713934338(Long), 2(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e700eba]
2021-07-05 02:51:58.440  INFO 42140 --- [           main] com.angus.MybatisPlusApplicationTests    : Guardian(id=1411754209713934338, name=铁锤霉霉, age=null, email=737093684@qq.com, version=3, isDelete=0, createTime=Mon Jul 05 02:30:07 CST 2021, updateTime=Mon Jul 05 02:35:12 CST 2021)

更新语句:update_time =now()

UPDATE guardian SET name=?, email=?, version=?, is_delete=?, create_time=?, update_time=now() WHERE id=? AND version=?

2、修改   MyMetaObjectHandler.java

 @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
       // this.strictUpdateFill(metaObject, "updateTime", () ->new Date() , Date.class);

        this.setFieldValByName("updateTime",new Date(),metaObject);
    }

 @Test
    void testOptimisticLocker(){
        // 1、查询数据
        Guardian guardian = guardianMapper.selectById(1411754209713934338L);
        //修改数据
        guardian.setName("熬夜giegie");
        //执行修改
        int i = guardianMapper.updateById(guardian);
       log.info(guardian.toString());

    }

结果:

查看日志

JDBC Connection [HikariProxyConnection@1005373776 wrapping com.mysql.cj.jdbc.ConnectionImpl@6b9fdbc6] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,version,is_delete,create_time,update_time FROM guardian WHERE id=?
==> Parameters: 1411754209713934338(Long)
<==    Columns: id, name, age, email, version, is_delete, create_time, update_time
<==        Row: 1411754209713934338, 铁锤霉霉, null, 737093684@qq.com, 3, 0, 2021-07-05 02:30:07, 2021-07-05 02:51:58
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@411a5965]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5b202ff] was not registered for synchronization because synchronization is not active
2021-07-05 02:59:42.343  INFO 42036 --- [           main] com.angus.handler.MyMetaObjectHandler    : start update fill ....
JDBC Connection [HikariProxyConnection@2040008077 wrapping com.mysql.cj.jdbc.ConnectionImpl@6b9fdbc6] will not be managed by Spring
==>  Preparing: UPDATE guardian SET name=?, email=?, version=?, is_delete=?, create_time=?, update_time=? WHERE id=? AND version=?
==> Parameters: 熬夜giegie(String), 737093684@qq.com(String), 4(Integer), 0(Integer), 2021-07-05 02:30:07.0(Timestamp), 2021-07-05 02:59:42.343(Timestamp), 1411754209713934338(Long), 3(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5b202ff]
2021-07-05 02:59:42.563  INFO 42036 --- [           main] com.angus.MybatisPlusApplicationTests    : Guardian(id=1411754209713934338, name=熬夜giegie, age=null, email=737093684@qq.com, version=4, isDelete=0, createTime=Mon Jul 05 02:30:07 CST 2021, updateTime=Mon Jul 05 02:59:42 CST 2021)

update_time 更新的值为  新时间修改数据库


模拟多线程环境下,测试乐观锁

//测试乐观锁(修改失败)
    @Test
    void testOptimisticLockerLose(){
        //查询数据库,获取监护人,修改信息
        Guardian guardian = guardianMapper.selectById(1411754209713934338L);
        guardian.setName("爱干饭angus");

        //插队线程 ,查询数据,修改
        Guardian guardian1 = guardianMapper.selectById(1411754209713934338L);
        guardian1.setName("duck_angus");
        int i = guardianMapper.updateById(guardian1);

        //执行修改
        int i1 = guardianMapper.updateById(guardian);


        log.info(guardian.toString());

    }

结果: 插队的修改成功

 控制台日志

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@17aa8a11] was not registered for synchronization because synchronization is not active
2021-07-05 03:10:23.356  INFO 42460 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-07-05 03:10:26.616  INFO 42460 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@257260599 wrapping com.mysql.cj.jdbc.ConnectionImpl@1a01d7f0] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,version,is_delete,create_time,update_time FROM guardian WHERE id=?
==> Parameters: 1411754209713934338(Long)
<==    Columns: id, name, age, email, version, is_delete, create_time, update_time
<==        Row: 1411754209713934338, 熬夜giegie, null, 737093684@qq.com, 4, 0, 2021-07-05 02:30:07, 2021-07-05 02:59:42
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@17aa8a11]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5b202ff] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1487424018 wrapping com.mysql.cj.jdbc.ConnectionImpl@1a01d7f0] will not be managed by Spring
==>  Preparing: SELECT id,name,age,email,version,is_delete,create_time,update_time FROM guardian WHERE id=?
==> Parameters: 1411754209713934338(Long)
<==    Columns: id, name, age, email, version, is_delete, create_time, update_time
<==        Row: 1411754209713934338, 熬夜giegie, null, 737093684@qq.com, 4, 0, 2021-07-05 02:30:07, 2021-07-05 02:59:42
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5b202ff]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@36068727] was not registered for synchronization because synchronization is not active
2021-07-05 03:10:27.376  INFO 42460 --- [           main] com.angus.handler.MyMetaObjectHandler    : start update fill ....
JDBC Connection [HikariProxyConnection@1901752553 wrapping com.mysql.cj.jdbc.ConnectionImpl@1a01d7f0] will not be managed by Spring
==>  Preparing: UPDATE guardian SET name=?, email=?, version=?, is_delete=?, create_time=?, update_time=? WHERE id=? AND version=?
==> Parameters: duck_angus(String), 737093684@qq.com(String), 5(Integer), 0(Integer), 2021-07-05 02:30:07.0(Timestamp), 2021-07-05 03:10:27.377(Timestamp), 1411754209713934338(Long), 4(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@36068727]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@799f916e] was not registered for synchronization because synchronization is not active
2021-07-05 03:10:27.512  INFO 42460 --- [           main] com.angus.handler.MyMetaObjectHandler    : start update fill ....
JDBC Connection [HikariProxyConnection@1466662004 wrapping com.mysql.cj.jdbc.ConnectionImpl@1a01d7f0] will not be managed by Spring
==>  Preparing: UPDATE guardian SET name=?, email=?, version=?, is_delete=?, create_time=?, update_time=? WHERE id=? AND version=?
==> Parameters: 爱干饭angus(String), 737093684@qq.com(String), 5(Integer), 0(Integer), 2021-07-05 02:30:07.0(Timestamp), 2021-07-05 03:10:27.512(Timestamp), 1411754209713934338(Long), 4(Integer)
<==    Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@799f916e]
2021-07-05 03:10:27.521  INFO 42460 --- [           main] com.angus.MybatisPlusApplicationTests    : Guardian(id=1411754209713934338, name=爱干饭angus, age=null, email=737093684@qq.com, version=5, isDelete=0, createTime=Mon Jul 05 02:30:07 CST 2021, updateTime=Mon Jul 05 03:10:27 CST 2021)

搞完,撒花,收工...

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值