龙空技术网

MybatisPlus新特性之逻辑删除、乐观锁、自动填充

学习万物君 4213

前言:

现在你们对“springboot mybatis自动提交”大致比较看重,朋友们都需要分析一些“springboot mybatis自动提交”的相关内容。那么小编在网上汇集了一些关于“springboot mybatis自动提交””的相关内容,希望同学们能喜欢,朋友们一起来了解一下吧!

MP特性公共字段的自动填充功能

自动更新全局属性,比如创建的时间修改的时间,这样就不用每执行一次插入更新操作都带上一个set大大节省了很多效率,从而也避免为了因为时间格式的不统一问题。

为了输出日志到控制台引入日志的依赖:

<dependency>         <groupId>org.slf4j</groupId>         <artifactId>slf4j-api</artifactId>         <version>1.7.25</version> </dependency> <dependency>         <groupId>org.slf4j</groupId>         <artifactId>slf4j-log4j12</artifactId>         <version>1.7.25</version> </dependency>

下面是两种更新的操作方式:

传统的通过插入时间操作的:

 private Date createTime; private Date updateTime;  user.setCreateTime(new Date()); user.setUpdateTime(new Date());
现代的通过配置全局字段来进行自动更新操作(MyBatis-Plus的特性-自动填充)

官方文档:

1.实现接口:com.baomidou.mybatisplus.mapper.IMetaObjectHandler实现方法

 public class MyMetaObjectHandler implements MetaObjectHandler {     //插入时的时间填充     @Override     public void insertFill(MetaObject metaObject) {     }      //更新时的时间填充     @Override     public void updateFill(MetaObject metaObject) {     } }

2.注解填充字段 @TableField(.. fill = FieldFill.INSERT) 生成器策略部分也可以配置!

  // 注意!这里需要标记为填充字段     @TableField(.. fill = FieldFill.INSERT)     private String fillField;

3.完整代码

@Slf4j//log打印日志,>>>>放弃你的System.out.println() @Component//申明是一个配置类 public class MyMetaObjectHandler implements MetaObjectHandler {       //插入时的时间填充       @Override       public void insertFill(MetaObject metaObject) {             log.info("start insert Fill");             this.fillStrategy(metaObject,"createTime",new Date());             this.fillStrategy(metaObject,"updateTime",new Date());       }        //更新时的时间填充       @Override       public void updateFill(MetaObject metaObject) {             log.info("start update Fill");             this.fillStrategy(metaObject,"updateTime",new Date());             //下面的setFieldValByName已经过时            //this.setFieldValByName("updateTime",new Date(), metaObject);       } }

测试:

 @Test public void insertUser(){       System.out.println(("----- insertUser method test ------"));       User user = new User();       user.setName("自动填充");       user.setEmail("自动填充p@email.com");       user.setPassword("自动填充");       user.setPhoneNum("12341234123");       int num = userMapper.insert(user);      if (num>0){             System.out.println("插入成功!!!");       }}  @Test public void updateUser(){      System.out.println(("----- updateUser method test ------")                );     User user = new User();     user.setId(6);     user.setEmail("mp@email.com");     user.setPassword("修改mp123");     int num = userMapper.updateById(user);     if (num>0){         System.out.println("修改成功!!!");     } }

此时在数据库中查看会发现在没有set creatTime和updateTime依然能正常操作。

乐观锁

那么说到乐观了肯定就会想到悲观,那么你想对了,还有悲观锁,简单描述一下两种锁:

乐观锁:乐观锁( Optimistic Locking)其实是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。上面提到的乐观锁的概念中其实已经阐述了他的具体实现细节:主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是Compare and Swap(CAS)。悲观锁:总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。这样注定会有一个问题就是性能损耗,所以很少用。

这样就延申出了CAS的一个ABA的问题,简述一下:

当A和B同时操作一个数据,而B的线程抢先一步完成,那么A在修改,这样造成数据不统一。

下面就是解决这个问题的办法。

适应场景

意图:

当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

取出记录时,获取当前version更新时,带上这个version执行更新时, set version = yourVersion+1 where version = yourVersion如果version不对,就更新失败乐观锁配置1.配置插件

spring xml配置

 <bean class="com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor"/>

spring boot配置

 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() {       return new OptimisticLockerInterceptor(); }

完整代码:

@MapperScan("com.cms.mapper")//扫描Mapper文件 @EnableTransactionManagement//事务注解 @Configuration//申明此类为配置类 public class MyBatisPlusConfig {        //配置乐观锁插件       @Bean       public OptimisticLockerInterceptor optimisticLockerInterceptor() {             return new OptimisticLockerInterceptor();       } }
2.注解实体字段 @Version
 public class User {        @Version        private Integer version; }

特别说明:仅支持int,Integer,long,Long,Date,Timestamp

3.数据库中也增加相应的字段。示例

如果系统没有并发的话那就不考虑乐观锁了,单点测试(一条一条数据的操作)就忽略,事实上系统中并发是必然存在的

单线程测试

 @Test public void OptimisticLockerInterceptorTest(){        System.out.println(("----- OptimisticLockerInterceptorTest method test ------"));        User user = new User();       user = userMapper.selectById(3);        user.setName("乐观");        user.setEmail("乐观@email.com");       user.setPassword("乐观");        int num = userMapper.updateById(user);        if (num>0){             System.out.println("修改成功!!!");       }  }
多线程测试没有加锁
@Test public void NoOptimisticLockerInterceptorTest(){      System.out.println(("----- NoOptimisticLockerInterceptorTest method test ------"));       User user1 = userMapper.selectById(3);       user1.setName("乐观1");       user1.setEmail("乐观1@email.com");       user1.setPassword("乐观1");        User user2 = userMapper.selectById(3);       user2.setName("乐观2");       user2.setEmail("乐观2@email.com");       user2.setPassword("乐观2");       //模拟线程2先执行  --此时我们先去掉乐观锁的注解      /*               //@Version               private Integer version;            */       userMapper.updateById(user2);       userMapper.updateById(user1);}

数据库中:

此时会发现:user1会覆盖user2的值。

加锁

@Test public void LockOptimisticLockerInterceptorTest(){       System.out.println(("----- LockOptimisticLockerInterceptorTest method test ------"));       User user1 = userMapper.selectById(3);       user1.setName("乐观1");      user1.setEmail("乐观1@email.com");       user1.setPassword("乐观1");        User user2 = userMapper.selectById(3);       user2.setName("乐观2");      user2.setEmail("乐观2@email.com");       user2.setPassword("乐观2");       //模拟线程2先执行  --加上乐观锁      /*               @Version              private Integer version;            */       userMapper.updateById(user2);        userMapper.updateById(user1);//加锁之后会发现user1并没有覆盖user2的值 }

此时会发现vserion的版本由1变成2,user1执行update操作失败,并没有覆盖user2的值。

示例SQL原理

 update table set x=x+1, version=version+1 where id=#{id} and version=#{version};  
version方式:

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。而MybatisPlus的乐观锁插件就是使用version的方式。

CAS操作方式:

即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则用新值更新,若失败则重试,一般情况下是一个自旋操作,即不断的重试。

逻辑删除

适用场景:

逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。如果你需要再查出来就不应使用逻辑删除,而是以一个状态去表示。

如:员工离职,账号被锁定等都应该是一个状态字段,此种场景不应使用逻辑删除。

若确需查找删除数据,如老板需要查看历史所有数据的统计汇总信息,请单独手写sql。SpringBoot 配置方式:application.yml 加入配置(如果你的默认值和mp默认的一样,该配置可无):

mybatis-plus:   global-config:     db-config:       logic-delete-field: flag  #全局逻辑删除字段值 3.3.0开始支持,详情看下面。       logic-delete-value: 1 # 逻辑已删除值(默认为 1)       logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
实体类字段上加上@TableLogic注解
@TableLogic private Integer deleted;
效果: 使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会)
 example 删除 update user set deleted=1 where id =1 and deleted=0 查找 select * from user where deleted=0
全局逻辑删除: begin 3.3.0如果公司代码比较规范,比如统一了全局都是flag为逻辑删除字段。使用此配置则不需要在实体类上添加 @TableLogic。但如果实体类上有 @TableLogic 则以实体上的为准,忽略全局。即先查找注解再查找全局,都没有则此表没有逻辑删除。
 mybatis-plus:   global-config:     db-config:       logic-delete-field: flag  #全局逻辑删除字段值
示例演示:
 //逻辑删除 @Testpublic void deletedTest(){      //随便删除一个用户       int count =  userMapper.deleteById("5");      if (count==0){             System.out.println("删除失败!!!");      } }

SQL语句:

数据库中的变化:

此时如果你在查询,如果不带条件(deleted=0)的话,肯定是查不到ID为5的用户,如果后期项目需要做统计就可以找到这种数据。

如果没有逻辑删除的话,那就直接把用户从数据库中移除,也就没有后期。

标签: #springboot mybatis自动提交 #mybatis 自动提交 #关闭mybatis自动提交 #关闭mybatisplus日志