顶部左侧内容
百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 在线教程 > 正文

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

gosiye 2024-08-19 14:53 9 浏览 0 评论

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的特性-自动填充

官方文档:https://baomidou.gitee.io/mybatis-plus-doc/#/auto-fill

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 creatTimeupdateTime依然能正常操作。

乐观

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

  • 乐观锁:乐观锁( 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  #全局逻辑删除字段值

示例演示:

 //逻辑删除 
@Test
public void deletedTest(){    
  //随便删除一个用户     
  int count =  userMapper.deleteById("5");    
  if (count==0){         
    System.out.println("删除失败!!!");    
  } 
}

SQL语句:

数据库中的变化:

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

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

相关推荐

全球最大的H5网站模板库(h5页面模板下载)

当今社会,互联网迅猛发展,在网络营销中,客户往往通过企业的网站建设留下对该企业的第一印象,一个优秀的企业网站已成为企业发展的重要纽带,嗨创H5,拥有国内外一流的技术团队,潜心专研网站建设6年,是全球最...

wordpress集团公司网站模板:XSgr(wordpress建站公司)

小兽wordpress推出一款高端集团公司主题,打造高品质官网。高端是一种态度和坚持,因为我坚信贴合产品及品牌理念的高端深度定制才能最大化地呈现企业的务实严谨与产品的专业品质相比,某种程度上讲–...

私心推荐,小编酷爱的五款高逼格网站模板

建站宝盒的网站模板上千套之多,各有各的风格色彩,但是,弱水三千,小编我却只取一瓢饮,在这上千套模板之中,小编酷爱的网站模板有五套,让小编私心推荐一下吧!1、茶叶贸易公司网站模板小编对这款网站模板可是一...

「书讯」政府网站用户行为研究与应用

《政府网站用户行为研究与应用》作者:刘合翔著出版日期:2018年6月开本:16开出版社:经济管理出版社小编推荐《政府网站用户行为研究与应用》的主题是关于政府网站用户行为的特征规律及其在政府网站优...

免费服务器-搭建模板网站的操作流程(图文版)

之前发文《创业者的官网:如何搭建免费云服务器及操作面板(图文版)》,因为做了视频才发现,创业者对视频的需求,远远低于对图文解说的需求。因此,补充图文教程,不清楚的看官们,可以直接看视频版本进行细部学...

快收藏这些高逼格H5网站模板吧,不绕弯子直接下载

上面这些响应式H5网站是不是很炫酷,比起那些“在线一键生成”是不是好太多了?关键是,那些一键制作都不会开放源码给你,自定义性也很局限。不过说到底还是难看。今天笔者推荐大家一个模板网站,全都是高质量的响...

如何开发网站建设管理系统模板(如何开发网站建设管理系统模板图片)

根据用户网站需求文档设计美工图,并设计数据库结构,让网站开发人员可以更多地关注前台美工,先对照美工图,编写静态HTML页面,按网站建设管理系统模板语法,修改编写好的静态HTML页面,运行。不再需要对...

C语言的数据类型介绍(c语言的数据类型介绍是什么)

在计算机系统中,数据是放在内存中的,数字、文字、符号、图形、音频、视频等数据都是以二进制形式存储在内存中的,它们并没有本质上的区别,那么0001000该理解为数字8呢,还是图像中某个像素的颜色...

C 语言格式化输出函数中常用的格式符号

在之前介绍输入输出函数的文章中,有提到格式化输入输出函数都有包含一种特殊的符号——格式符号。那篇文章中关于格式符号也只是一笔带过,没有进行深入挖掘。本篇文章主要对输出函数(printf)中的一些常用格...

C#中的类型转换(c#数据转换类)

计算机存储的基本单位:字节我们知道一个字节(Byte)有8个比特(bit)构成,比特是存储的最小单位,表示0和1,但为什么计算机存储的基本单位是字节,而不是比特呢?假设我们要存储数字3(二进制:11...

Java8中String内存空间占用分析(电脑里下载的文件怎样删除才不会占用内存空间)

1.前言分析之前,简单回顾一下对象的内存分布。在HotSpot虚拟机中,对象在堆内存中的存储布局可以划分为三部分:对象头、实例数据和对齐填充。对象头包含两部分内容:MarkWord和类型指针。实例数据...

「每日C语言」数据类型大小和取值范围

对于c语言来说,数据类型是一个很重要的概念和知识点,它涉及到的是内存的空间,这在和硬件交互的时候是非常重要的。K&R给出了7个数据类型相关的关键字,分别是:int、long、short、uns...

【c语言学习笔记】数据类型(c语言里面的数据类型)

c语言学习笔记,欢迎大家能在评论区提出我学习错误的地方方便我进行改正~在计算机中,计算机用二进制来储存数据,在c语言中有许多的数据类型用来存储数据,当然不同的数据类型所用的内存占用也不一样,下面就来用...

关于MySQL varchar类型最大值,原来一直都理解错了

我是架构精进之路,点击上方“关注”,坚持每天为你分享技术干货,私信我回复“01”,送你一份程序员成长进阶大礼包。写在前面关于MySQLvarchar字段类型的最大值计算,也许我们一直都理解错误了,...

C语言数据类型的转换(c语言数据类型的转换方式)

类型转换在C语言程序中,经常需要对不同类型的数据进行运算,为了解决数据类型不一致的问题,需要对数据的类型进行转换。例如一个浮点数和一个整数相加,必须先将两个数转换成同一类型。C语言程序中的类型...

取消回复欢迎 发表评论: