• MybatisPlus学习


    一.引言

    MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率。根据所学知识,有以下三种开发方式 :

    基于MyBatis使用MyBatisPlus

    基于Spring使用MyBatisPlus

    基于SpringBoot使用MyBatisPlus

    首先Mybatis基于boot整合时的步骤如下

    1.创建boot工程,勾选所需要的技术

    2.设置dataSource相关的属性

    3.定义数据层接口映射配置(UserDao)

    二.Boot整合MybatisPlus步骤 

    1.创建Boot工程,勾选所需技术

    由于MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加

    2.pom文件导入依赖

    1. <dependency>
    2. <groupId>com.baomidou</groupId>
    3. <artifactId>mybatis-plus-boot-starter</artifactId>
    4. <version>3.4.1</version>
    5. </dependency>

    从MP的依赖关系可以看出,通过依赖传递已经将MyBatis与MyBatis整合Spring的jar包导入, 所以不需要额外在添加MyBatis的相关jar包

    3.添加配置信息

    修改resource文件夹下的application.propertes为application.yml文件

    4.根据数据库创建实体类User

    在domain路径下创建User类

    1. public class User {
    2. private Long id;
    3. private String name;
    4. private String password;
    5. private Integer age;
    6. private String tel;
    7. //getter,setter,toString方法
    8. }

    5.在dao路径下创建UserDao接口(数据层操作接口)

    继承BaseMapper

    1. @Mapper
    2. public interface UserDao extends BaseMapper<User> {
    3. }

    6.测试类下测试dao功能

    自动装配UserDao

    编写测试功能

    1. @SpringBootTest
    2. class MybatisPlusDemoApplicationTests {
    3. @Autowired
    4. private UserDao userDao;
    5. @Test
    6. void testGetAll() {
    7. List<User> userList = userDao.selectList(null);
    8. System.out.println(userList);
    9. }
    10. }

     这一步可以发现,userDao中因为继承BaseMapper,可以选择很多方法

    总结:MP的开发过程中,和mybatis不同在于数据层接口需要继承BaseMapper<>,不需要手动写增删改查语句了

    三.MP数据层开发

    数据层开发的需要实现的功能是增删改查,也就是标准CRUD(增删改查)开发。

    对于标准的CRUD功能开发,MP提供了以下的方法来实现

     1.新增

    对应了MP中提供的insert()方法,

    1. @Test
    2. void testSave() {
    3. User user = new User();
    4. user.setName("黑马程序员");
    5. user.setPassword("itheima");
    6. user.setAge(12);
    7. user.setTel("4006184000");
    8. userDao.insert(user);
    9. }

    在新增功能中,没有set对应的id,此时会自动生成一个id,但是不会对应主键自增,这是MP的主键生成策略。

    2.删除

    int deleteById (Serializable id)

    参数类型是一个序列化类Serializable,

    String和Number是Serializable的子类,Number又是Float,Double,Integer等类的父类

    MP使用Serializable作为参数类型,就好比我们可以用Object接收任何数据类型一样。

    1. @Test
    2. void testDelete() {
    3. userDao.deleteById(1401856123725713409L);
    4. }

    3.修改

    1. @Test
    2. void testUpdate() {
    3. User user = new User();
    4. user.setId(1L);
    5. user.setName("Tom888");
    6. user.setPassword("tom888");
    7. userDao.updateById(user);
    8. }

    updateById()是根据Id进行修改,所以传入的对象中需要有Id属性值

    4.根据id查询

    1. @Test
    2. void testGetById() {
    3. User user = userDao.selectById(2L);
    4. System.out.println(user);
    5. }

    5.全部查询

    selectList()方法返回的是一个List类型的数据

    1. @Test
    2. void testGetAll() {
    3. List userList = userDao.selectList(null);
    4. System.out.println(userList);
    5. }

    6.分页查询

    selectPage() ,查看源码(ctrl+鼠标左键)发现,这个方法返回的是一个Ipage对象 

    所以我们先定义一个IPage对象,但是IPage是一个接口无法创建对象,所以需要一个IPage的实现类,查看IPage的源码(ctrl+alt+b)

    发现其实现类只有一个Page,那么就用Page实现类来实现Ipage。

    将实现的Ipage传入selectPage()方法中,实现按页查询。

    Ipage中存在的查询方法如下

    1. //分页查询
    2. @Test
    3. void testSelectPage(){
    4. //1 创建IPage分页对象,设置分页参数,1为当前页码,2为每页显示的记录数
    5. IPage page=new Page<>(1,2);
    6. //2 执行分页查询
    7. userDao.selectPage(page,null);
    8. //3 获取分页结果
    9. System.out.println("当前页码值:"+page.getCurrent());
    10. System.out.println("每页显示数:"+page.getSize());
    11. System.out.println("一共多少页:"+page.getPages());
    12. System.out.println("一共多少条数据:"+page.getTotal());
    13. System.out.println("数据:"+page.getRecords());
    14. }

    执行后发现,还是将所有的数据查询了出来 ,按照参数应该查询第一页的两条数据。

    设置分页拦截器即可解决以上问题,MP已经将拦截器设置好,我们只需配置成Spring管理的Bean对象即可。

     配置类的写法

    修改配置文件,打开MP执行的SQL日志

    1. #打印sql日志到控制台
    2. mybatis-plus:
    3. configuration:
    4. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

    再次运行测试类,就可以看到SQL日志 

    select id,name,password,age,tel from user limit ?,?

    //根据页码参数查询,limit?,?表示需要进行两次运算,查询第?(>1)页,

    limit?表示查询第一页。

    四.Lombok

    Lombok是一个java库,提供了一组注解,简化了POJO实体类开发

    就是实体类中的getter,setter,toString,构造函数方法,私有属性可以在Lombok的帮助下,不用书写了

    1.添加Lombok依赖

    pom文件中导入lombok坐标

    2.实体类中添加lombok注解

    1. @Setter
    2. @Getter
    3. @ToString
    4. public class User {
    5. private Long id;
    6. private String name;
    7. private String password;
    8. private Integer age;
    9. private String tel;
    10. }

    @Setter:为模型类的属性提供setter方法

    @Getter:为模型类的属性提供getter方法

    @ToString:为模型类的属性提供toString方法

    @EqualsAndHashCode:为模型类的属性提供equals和hashcode方法

    @Data:是个组合注解,包含上面的注解的功能

    @NoArgsConstructor:提供一个无参构造函数

    @AllArgsConstructor:提供一个包含所有参数的构造函数

    Lombok的注解还有很多,最后三个是比较常用的。

    当然,也可以使用自己编写的构造方法和Lombok注解同时存在。

    自己定义一个有参的构造方法,同时也存在Lombok插件的案例如下:

    1. @Data
    2. @AllArgsConstructor
    3. @NoArgsConstructor
    4. public class User {
    5. private Long id;
    6. private String name;
    7. private String password;
    8. private Integer age;
    9. private String tel;
    10. public User(String name, String password) {
    11. this.name = name;
    12. this.password = password;
    13. }
    14. }

    五.DQL编程控制

    1.取消Spring初始日志打印

    新建logback.xml文件

    2.取消Spring日志打印banner,取消Mp日志启动banner

    3.Wrapper条件查询

    查看接口的实现类:ctrl H

    实现类也有很多,说明有多种构建查询条件对象的方式,

    第一种

    lt;小于

    1. //按条件查询
    2. QueryWrapper qw = new QueryWrapper();
    3. //SELECT id,name,password,age,tel FROM user WHERE (age < ?),lt:小于,
    4. qw.lt("age",18);
    5. List<User> userList = userDao.selectList(qw);
    6. System.out.println(userList);

    第二种;lambda方式,改善了条件语句种参数容易写错的问题(注意泛型)

    1. //lambda按条件查询
    2. QueryWrapper<User> qw = new QueryWrapper<User>();
    3. qw.lambda().lt(User::getAge,10);
    4. List<User> userList = userDao.selectList(qw);
    5. System.out.println(userList);

    第三种:在QueryWrapper基础上升级的lambda方式

    省去了qw后需要调用一层lambda()的方式

    注意:构建LambdaQueryWrapper的时候泛型不能省。 

    1. LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    2. lqw.lt(User::getAge,10);
    3. List<User> userList = userDao.selectList(lqw);
    4. System.out.println(userList);

    4.多条件构建

    需求:查询数据库表中,年龄在10岁到30岁之间的用户信息

    解决方案:

    SQL语句是:SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)

    gt:大于,lt:小于

    在Mp的语言规范中,实现以上sql的语法是

    1. //多条件查询
    2. LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    3. lqw.lt(User::getAge,30);
    4. lqw.gt(User::getAge,10);
    5. //构建多条件查询,可以使用链式编程
    6. //lqw.lt(User::getAge, 30).gt(User::getAge, 10);
    7. List<User> userList = userDao.selectList(lqw);
    8. System.out.println(userList);

    需求:查询数据库表中,年龄小于10或年龄大于30的数据

    以上需求的SQL语句是:

    SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
    

    转化为Mp风格是

    1. LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    2. lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
    3. List<User> userList = userDao.selectList(lqw);
    4. System.out.println(userList);

    5.NULL判定

    一般查询会在后台做两个条件,如果查询只给了一个条件,即小于**但是大于未输入,大于**但是小于未输入。

    这时候未输入的条件就是空值,我们就需要做空值判定 ,否则就会出现问题

    1. //模拟页面传递过来的查询数据
    2. UserQuery uq = new UserQuery();
    3. //模拟null值传入
    4. //uq.setAge(10);
    5. uq.setAge2(30);
    6. //多条件查询
    7. LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    8. lqw.lt(User::getAge,uq.getAge2()).gt(User::getAge,uq.getAge());
    9. List<User> userList = userDao.selectList(lqw);
    10. System.out.println(userList);

    报错如下 

     所以要进行null值判断

    1. //模拟页面传递过来的查询数据
    2. UserQuery uq = new UserQuery();
    3. // uq.setAge(10);
    4. uq.setAge2(30);
    5. //多条件查询
    6. LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    7. //先判断第一个参数是不是ture,如果是true再连接语句
    8. lqw.lt(null!=uq.getAge2(),User::getAge, uq.getAge2());
    9. lqw.gt(null!=uq.getAge(),User::getAge, uq.getAge());
    10. List<User> userList = userDao.selectList(lqw);
    11. System.out.println(userList);

    6.查询投影

    如果想在数据库中查询某些字段,可以使用投影查询

    6.1查询指定字段

    1. //查询投影,使用lambda风格
    2. LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    3. lqw.select(User::getName);
    4. List<User> userList = userDao.selectList(lqw);
    5. System.out.println(userList);
    6. //不使用lambda风格
    7. QueryWrapper<User> qw = new QueryWrapper<User>();
    8. qw.select("name");
    9. List<User> userList = userDao.selectList(qw);
    10. System.out.println(userList);

    差距就是lambda风格是直接使用方法对字段进行查询,无需知道字段名

    直接使用select需要知道字段名才能进行查询

    6.2聚合查询 

    count:总记录数         max:最大值          min:最小值          avg:平均值              sum:求和

    1. QueryWrapper<User> lqw = new QueryWrapper<User>();
    2. //lqw.select("count(*) as count");
    3. //SELECT count(*) as count FROM user
    4. //lqw.select("max(age) as maxAge");
    5. //SELECT max(age) as maxAge FROM user
    6. //lqw.select("min(age) as minAge");
    7. //SELECT min(age) as minAge FROM user
    8. //lqw.select("sum(age) as sumAge");
    9. //SELECT sum(age) as sumAge FROM user
    10. lqw.select("avg(age) as avgAge");
    11. //SELECT avg(age) as avgAge FROM user
    12. List<Map<String, Object>> userList = userDao.selectMaps(lqw);
    13. System.out.println(userList);
    1. // 聚合查询
    2. QueryWrapper<User> qw = new QueryWrapper<User>();
    3. qw.select("count(*) as count,age");
    4. qw.groupBy("age");
    5. // List<User> userList = userDao.selectList(qw);
    6. List<Map<String,Object>> userList2 = userDao.selectMaps(qw);
    7. System.out.println(userList2);

    聚合与分组查询,无法使用lambda表达式来完成

    MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实 现 

    7.查询条件

    7.1等值查询

    例如,登录时想要验证用户名和密码,这时候可以使用等值查询

    1. //条件查询
    2. LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    3. lqw.eq(User::getName,"Jerry").eq(User::getPassword,"jerry1");
    4. User loginuser = userDao.selectOne(lqw);
    5. System.out.println(loginuser);

    .eq()方法,相当于sql语句中的

    SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)

    7.2范围查询

    1. LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    2. lqw.between(User::getAge, 10, 30);
    3. List<User> userList = userDao.selectList(lqw);
    4. System.out.println(userList);

    .between()对应sql

    1. //SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND
    2. ?)

    gt():大于(>)

    ge():大于等于(>=)

    lt():小于(<)

    lte():小于等于(<=)

    between():between ? and ?

    7.3模糊查询

    查询名字是J开头的信息

    1. LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
    2. lqw.likeLeft(User::getName, "J");
    3. //SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
    4. List<User> userList = userDao.selectList(lqw);
    5. System.out.println(userList);

    对应到sql中的模糊查询:

    like():前后加百分号,如 %J%

    likeLeft():前面加百分号,如 %J

    likeRight():后面加百分号,如 J%

    后续的条件查询还有很多中,如有需要,查阅官方文档

    https://mp.baomidou.com/guide/wrapper.html#abstractwrapper

    六.后端和数据库映射匹配兼容性

    我们建立的模型类中封装了属性,这些属性恰好是和数据库中的字段对应的,但是如果模型类的属性名和数据库中的字段名不一致,无法获取对象并封装到模型类中,这时候可以通过MP的注解来解决

    6.1表名不一致

    @TableName

    如果模型类是User类,MP默认情况下会使用模型类的类名首字母小写当表名使用,那么就会直接查询user表,但是数据库中存在的是t_user,所以使用@TableName注解

     6.2表中字段不一致

    MP默认情况下会使用模型类的属性名当做表的列名使用,如果表中的字段不是password,可以使用@TableField()注解

     6.3数据库中不存在对应属性

    MP默认情况下会查询模型类的所有属性对应的数据库表的列,这个时候如果模型类中存在某个字段,但数据库中不存在,就会报错

    @TableField(exist=false)

    使用该注解排除字段

    6.4隐藏查询字段 

    如果存在某些敏感属性,不想在查询中展示出来,同样使用注解

    七.Id生成策略控制 (@TableId)

    在前面的操作中,如果我们新增一条记录,这个时候不指定id的话,id的值会是一段很长的字符串,如果想要按照数据库表字段进行自增长,可以在数据库更改选项,设置自动递增的起始值,注意表中不能存在比起始值更大的数值。

    如果无法修改这个选项,可以运行以下命令

    ANALYZE TABLE 'table_name'

     然后修改模型类,用到注解@TableId,这个注解的功能是设置主键属性的生成策略,生成策略根据实际需求可以存在多种,例如

    根据不同的需求,模型类中可以添加不同的注解

    7.1.AUTO生成策略

    设置为数据库中默认的自动增长 ,根据数据库中的自动递增选项作为初值,我们插入记录时,主键根据这个初值自动递增。

    要确保对应的 数据库表设置了ID主键自增,否则无效

    查看其他的策略用法,进入源码,如果没有中文注释,点击右上角的Download Sources ,会自动把这个类的java文件下载下来,就能看到具体的注释内容。

    NONE: 不设置id生成策略

    INPUT:用户手工输入id

    ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型)

    ASSIGN_UUID:以UUID生成算法作为id生成策略

    其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替掉。如下图已过时

    7.2 INPUT生成策略

    input生成策略是自己输入id,这个时候

    1.需要修改数据库设置,将id的主键 自动递增选项关闭

    2.手动输入id

    7.3 ASSIGN_ID生成策略

    1.修改生成策略是ASSIGN_ID

    不需要手动设置id

    7.4 ASSIGN_UUID

    使用uuid需要注意的是,主键的类型不能是Long,而应该改成String类型

    数据库中更改设置

    7.5雪花算法

     雪花算法(SnowFlake),是Twitter官方给出的算法实现, 是用Scala写的。其生成的结果是一个64bit大小整数,它的结构如下图:

    1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数, 所以最高位固定为0。

    2. 41bit-时间戳,用来记录时间戳,毫秒级

    3. 10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID其取值范围0-31,低位 5bit是工作节点ID其取值范围0-31,两个组合起来最多可以容纳1024个节点

    4. 序列号占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096 个ID

    生成策略分析

    根据以下分析,确定使用何种生成策略

    NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设 置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很 多判定,实现起来比较复杂

    AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用

    ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符 串,长度过长占用空间而且还不能排序,查询性能也慢

    ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是 生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键 综上所述,每一种主键策略都有自己的优缺点,根据自己项目业务的实际情况来选择使用才是最明 智的选择

    7.6 简化配置

    1.如果存在多个模型类,所有的模型类都是用一种生成策略

     可以在配置文件中添加配置,让所有的模型类都用相同的主键生成策略

    这样所有的模型类中的@TableId可以不用写了

    2.数据库表和模型类是存在映射关系的

    一般在数据库中存在的都是相同的前缀

    MP会默认将模型类的类名名首字母小写作为表名使用,假如数据库表的名称都以t_开头,那么我们 就需要将所有的模型类上添加@TableName

    在配置类中添加以下配置,模型类中的@TableName可以不用写了

    八. 多记录操作

    可以单独删除一条数据,单独查看,增加一条数据。如果想一次操作多条数据,可以使用BatchIds操作

    8.1 删除多条数据

    1. @Test
    2. void testDelete(){
    3. List<Long> list = new ArrayList<>();
    4. list.add(1589814053571514370L);
    5. list.add(1589819565230788610L);
    6. userDao.deleteBatchIds(list);
    7. }

    deleteBatchIds需要的是一个list类型的参数,所以首先定义一个list,然后将需要操作的记录的id加入list中,再执行方法,就可以批量操作记录

    8.2 查询多条数据

    1. @Test
    2. void testGetByIds(){
    3. //查询指定多条数据
    4. List<Long> list = new ArrayList<>();
    5. list.add(1L);
    6. list.add(3L);
    7. list.add(4L);
    8. userDao.selectBatchIds(list);
    9. }

    九.逻辑删除

    当数据库中存在多张表时,多张表会存在关联字段,设置外键之后,删除一张表中的某个记录,会把其他表的关联字段的记录同时删除。

    如果我们需要其他表的字段数据记录,就不能使用这种删除方式。

    例如以下案列 

    这是一个员工和其所签的合同表,关系是一个员工可以签多个合同,是一个一(员工)对多(合同) 的表

    员工ID为1的张业绩,总共签了三个合同,如果此时他离职了,我们需要将员工表中的数据进行删除,

    会执行delete操作 如果表在设计的时候有主外键关系,那么同时也得将合同表中的前三条数据也删除掉

    后期要统计所签合同的总金额,就会发现对不上,原因是已经将员工1签的合同信息删除掉了

    如果只删除员工不删除合同表数据,那么合同的员工编号对应的员工信息不存在,那么就会出现垃圾数据,就会出现无主合同,根本不知道有张业绩这个人的存在

    所以经过分析,我们不应该将表中的数据删除掉,而是需要进行保留,但是又得把离职的人和在职的人进行区分,这样就解决了上述问题,如:

     

     区分的方式,就是在员工表中添加一列数据deleted,如果为0说明在职员工,如果离职则将其改为1(0和1所代表的含义是可以自定义的)

    物理删除:业务数据从数据库中丢弃,执行的是delete操作

    逻辑删除(logic):为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库 中,执行的是update操作

    实现步骤

    1.修改数据库表结构,添加逻辑字段

    2.修改模型类,添加属性值

    1. //逻辑删除的默认值,value为正常数据,delval为删除数据
    2. @TableLogic(value = "0",delval = "1")
    3. private Integer deleted;

    3.运行delete方法

    发现.delete()方法使用的是update语句,会将指定的字段修改成删除状态对应的逻辑值

    思考:

    1.逻辑删除后,再进行查询出现什么情况

    查询会自带一个where限制,不会显示将已经被删除掉的数据

    如果想要查询所有数据,包括被删除的数据:

    在userDao中定义一个查询全部的方法,在测试类中进行实现即可

    1. @Mapper
    2. public interface UserDao extends BaseMapper<User> {
    3. @Select("select * from t_user")
    4. public List selectAll();
    5. }
    1. @Test
    2. void selectAll(){
    3. System.out.println(userDao.selectAll());
    4. }

    2.如果每个表都有逻辑删除,每个模型类都要添加@TableLogic(),可以使用修改全局配置来简化代码

    1. #逻辑删除字段名
    2. logic-delete-field: deleted
    3. #逻辑删除字面值:为删除为0
    4. logic-not-delete-value: 0
    5. #逻辑删除字面值:删除为1
    6. logic-delete-value: 1

    添加以上配置后,模型类中的注解就可以删除。

    十.乐观锁

    “锁”是用来处理并发问题的,乐观锁是用来防止一个记录在被a修改的同时,不被b修改

    业务并发现象带来的问题:秒杀

    假如有100个商品或者票在出售,为了能保证每个商品或者票只能被一个人购买,如何保证不会出现超买或者重复卖 对于这一类问题,其实有很多的解决方案可以使用

    第一个最先想到的就是锁,锁在一台服务器中是可以解决的,但是如果在多台服务器下锁就没有办法控制,比如12306有两台服务器在进行卖票,在两台服务器上都添加锁的话,那也有可能会导致在同一时刻有两个线程在进行卖票,还是会出现并发问题

    乐观锁这种方式是针对于小型企业的解决方案,因为数据库本身的性能就是个瓶颈,如果对其并发量超过2000以上的就需要考虑其他的解决方案了。

    实现思路:

    1.数据库中添加字段version,比如默认值给1

    2.a线程修改数据之前,取出记录时,获取当前数据库中的version=1,b线程要修改数据之前,取出记录时,获取当前数据库中的version=1

    3.线程执行时,version值的变化

    1. a线程执行时
    2. set version = newVersion where version = oldVersion
    3. newVersion = version+1 [2]
    4. oldVersion = version [1] ,
    5. b线程执行时,
    6. set version = newVersion where version = oldVersion
    7. newVersion = version+1 [2]
    8. oldVersion = version [1]

     无论哪个线程先执行,就会将version值加一

    4.a,b开始执行

    1. 假如a线程先执行更新,会把version改为2
    2. b线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以b线程会修改失败
    3. 假如b线程先执行更新,会把version改为2
    4. a线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以a线程会修改失败

    不管谁先执行都会确保只能有一个线程更新数据,这就是MP提供的乐观锁的实现原理分析。

    实现步骤

    1.数据库表中添加version字段,默认为1

    2.模型类中添加对应的属性

    1. @Version
    2. private Integer version;

    3.添加乐观锁的拦截器

    1. @Configuration
    2. public class MPConfig {
    3. @Bean
    4. public MybatisPlusInterceptor mpInterceptor(){
    5. //1.定义MP拦截器
    6. MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
    7. //2.添加乐观锁拦截器
    8. mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    9. return mpInterceptor;
    10. }
    11. }

    拦截器的目的是拿到version值,并且自动操作version+1。 

    4.执行更新操作

    实现乐观锁的要点是version字段,所以首先要得到version字段,再通过拦截器对version自动操作

    1. @Test
    2. void testUpdate(){
    3. //首先拿到要操作记录(10)的所有信息
    4. User user = userDao.selectById(10L);
    5. //更新此条记录
    6. user.setName("乐观锁1");
    7. userDao.updateById(user);
    8. }

    观察以上日志,发现拦截器对version进行操作了,

    接下来模拟两个线程操作一个记录的场景

    1. @Test
    2. void testUpdate(){
    3. //首先拿到要操作记录(10)的所有信息
    4. User user = userDao.selectById(10L);
    5. User user2 = userDao.selectById(10L);
    6. //将更新的数据放入
    7. user2.setName("乐观锁2");
    8. userDao.updateById(user2);
    9. user.setName("乐观锁1");
    10. userDao.updateById(user);
    11. }

     乐观锁的官方文档:

    十一.代码生成器

    1.创建一个Spring的maven项目

     

  • 相关阅读:
    Mybatis缓存及高级映射
    springcloud程序启动后,nacos服务中心的服务名称与程序spring.application.name所配置的应用名不一致
    一个小型公司怎么落地微服务
    Nginx配置文件
    创建一个SpringCloud项目
    长短期记忆网络(LSTM)
    【react】点击空白处隐藏
    Clover引导都支持哪些.efi文件
    连续系统PID的Simulink仿真
    这短短 6 行代码你能数出几个bug?
  • 原文地址:https://blog.csdn.net/cvpatient/article/details/127209203