文章目录
- 一、MyBatisPlus入门案例与简介
- 1.入门案例
- 2.springboot整合mybatis的方式
- 3.springboot整合mybatisplus
- 步骤1.创建环境,上面我们已经创建过了
- 步骤2.创建数据库及表
- 步骤2.pom.xml补全依赖
- 步骤3.添加MP的相关配置信息
- 步骤4.根据数据库表创建实体类
- 步骤5.创建Dao接口
- 步骤6.编写引导类
- 步骤7.编写测试类
 
 
- 二、CRUD使用
- 1.新增
- 2.删除
- 3.修改
- 4.根据id查
- 5.查询所有
- 6.分页查询
 
- 三、DQL编程控制
- 1.条件查询
- 第一种:QueryWrapper
- 第二种:QueryWrapper的基础上使用lambda
- 第三种:LambdaQueryWrapper
 
- 2.多条件查询
- 3.查询投影字段
- 4.聚合查询
- 5.分组查询
- 6.等值查询
- 7.范围查询
- 8.模糊查询
- 9.排序查询
- 10.映射匹配兼容性
- 1.表字段与编码属性设计不同步
- 2.编码中添加了数据库中未定义的属性
- 3.设置查询权限,对于有的敏感信息不返回前端
- 4.表名与编码开发设计不同步
 
 
- 四、DML编程控制
- 1.id生成策略--AUTO
- 2.id生成策略--INPUT
- 3.id生成策略--ASSIGN_ID
- 4.id生成策略--ASSIGN_UUID
- 5.ID生成策略对比
 
- 五、多记录操作
- 1.批量删除
- 2.逻辑删除
- 1.修改数据库表添加`deleted`列
- 2.实体类添加属性
- 3.运行测试方法
 
 
- 六、乐观锁
- 1.数据库表添加列
- 2.在模型类中添加对应的属性
- 3.添加乐观锁的拦截器
- 4.执行更新操作
- ps:模拟一下加锁的情况
 
- 七、结语
 
主要掌握
基于MyBatisPlus完成标准Dao的增删改查功能
掌握MyBatisPlus中的分页及条件查询构建
掌握主键ID的生成策略
一、MyBatisPlus入门案例与简介
1.入门案例
- 创建springboot工程
  
  
-  MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率。 
-  开发方式 - 基于MyBatis使用MyBatisPlus
- 基于Spring使用MyBatisPlus
 
2.springboot整合mybatis的方式
参考我的这篇博客–>springboot整合mybatis的方式
3.springboot整合mybatisplus
步骤1.创建环境,上面我们已经创建过了
步骤2.创建数据库及表
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (
    id bigint(20) primary key auto_increment,
    name varchar(32) not null,
    password  varchar(32) not null,
    age int(3) not null ,
    tel varchar(32) not null
);
insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
步骤2.pom.xml补全依赖
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>
- druid数据源可以加也可以不加,SpringBoot有内置的数据源,可以配置成使用Druid数据源
步骤3.添加MP的相关配置信息

 说明:serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为Asia/Shanghai
步骤4.根据数据库表创建实体类
public class User {   
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    //setter...getter...toString方法略
}
步骤5.创建Dao接口
@Mapper
public interface UserDao extends BaseMapper<User> {
}
步骤6.编写引导类
@SpringBootApplication
//@MapperScan("com.example.dao")
public class Mybatisplus01Application {
    public static void main(String[] args) {
        SpringApplication.run(Mybatisplus01Application.class, args);
    }
}
**说明:**Dao接口要想被容器扫描到,有两种解决方案:
- 方案一:在Dao接口上添加@Mapper注解,并且确保Dao处在引导类所在包或其子包中- 该方案的缺点是需要在每一Dao接口中添加注解
 
- 方案二:在引导类上添加@MapperScan注解,其属性为所要扫描的Dao所在包- 该方案的好处是只需要写一次,则指定包下的所有Dao接口都能被扫描到,@Mapper就可以不写。
 
- 该方案的好处是只需要写一次,则指定包下的所有Dao接口都能被扫描到,
步骤7.编写测试类
这里以查询所有为例子
@SpringBootTest
class Mybatisplus01ApplicationTests {
    @Autowired
    UserDao userDao;
    @Test
    void testGetAll() {
        List<User> users = userDao.selectList(null);
        System.out.println(users);
    }
}
结果:
 
跟之前整合MyBatis相比,我们不需要在DAO接口中编写方法和SQL语句了,只需要继承BaseMapper接口即可。整体来说简化很多。
二、CRUD使用
对于标准的CRUD功能都有哪些以及MP都提供了哪些方法可以使用呢?
我们先来看张图:
 
1.新增
int insert (T t)
-  T:泛型,新增用来保存新增数据 
-  int:返回值,新增成功后返回1,没有新增成功返回的是0 
在测试类中进行新增操作:
    @Test
    void testSave() {
        User user = new User();
        user.setName("一只呆呆木");
        user.setPassword("!666");
        user.setAge(18);
        user.setTel("001");
        userDao.insert(user);
    }

但是数据中的主键ID,有点长,那这个主键ID是如何来的?我们更想要的是主键自增,应该是5才对,这个就涉及到mp的id生成策略的配置,后面有说~
2.删除
int deleteById (Serializable id)
Serializable:参数类型
-  思考:参数类型为什么是一个序列化类? 
  
 从这张图可以看出,
-  String和Number是Serializable的子类, 
-  Number又是Float,Double,Integer等类的父类, 
-  能作为主键的数据类型都已经是Serializable的子类, 
-  MP使用Serializable作为参数类型,就好比我们可以用Object接收任何数据类型一样。 
-  int:返回值类型,数据删除成功返回1,未删除数据返回0。 
    @Test
    void testDelete() {
        userDao.deleteById(1599248419765915649L);//这里的id后面要加L
    }
3.修改
int updateById(T t);
-  T:泛型,需要修改的数据内容,注意因为是根据ID进行修改,所以传入的对象中需要有ID属性值 
-  int:返回值,修改成功后返回1,未修改数据返回0 
在测试类中进行修改操作:
    @Test
    void testUpdate() {
        User user = new User();
        user.setId(1L);
        user.setName("Tom888");
        user.setPassword("tom888");
        userDao.updateById(user);
    }
修改前:
 
修改后:
 
 **说明:**修改的时候,只修改实体对象中有值的字段。
4.根据id查
T selectById (Serializable id)
- Serializable:参数类型,主键ID的值
- T:根据ID查询只会返回一条数据
在测试类中进行查询操作:
    @Test
    void testGetById() {
        User user = userDao.selectById(4L);
        System.out.println(user);
    }

5.查询所有
List<T> selectList(Wrapper<T> queryWrapper)
- Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
- List:因为查询的是所有,所以返回的数据是一个集合
在测试类中进行查询操作:
    @Test
    void testGetAll() {
        List<User> users = userDao.selectList(null);
        System.out.println(users);
    }
我们所调用的方法都是来自于DAO接口继承的BaseMapper类中。里面的方法有很多,这里列了几个基本的增删改查的。
6.分页查询
使用分页查询我们需要设置分页拦截器,否则将会查询所有
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
- IPage:用来构建分页查询条件
- Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
- IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage
IPage是一个接口,我们需要找到它的实现类来构建它,具体的实现类,可以进入到IPage类中按ctrl+h,会找到其有一个实现类为Page。
 
 测试类:
//分页查询
    @Test
    void testSelectPage(){
        //1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数
        IPage<User> page=new Page<>(1,3);
        //2 执行分页查询
        userDao.selectPage(page,null);
        //3 获取分页结果
        System.out.println("当前页码值:"+page.getCurrent());
        System.out.println("每页显示数:"+page.getSize());
        System.out.println("一共多少页:"+page.getPages());
        System.out.println("一共多少条数据:"+page.getTotal());
        System.out.println("数据:"+page.getRecords());
    }
使用分页查询我们需要设置分页拦截器,否则将会查询所有
 
我们明明查的一页中有三条数据,但是却给我们查到了所有的数据,这显然没有达到分页的目的,这个时候我们就需要设置分页拦截器。
设置分页拦截器:
这个拦截器MP已经为我们提供好了,我们只需要将其配置成Spring管理的bean对象即可。
创建一个config配置包,然后创建拦截器
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //1 创建MybatisPlusInterceptor拦截器对象
        MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();
        //2 添加分页拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}
运行测试程序:
 
三、DQL编程控制
1.条件查询
在进行查询的时候,我们的入口是在Wrapper这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式,
 
第一种:QueryWrapper
    @Test
    void testGetByCon(){
        QueryWrapper qw = new QueryWrapper();
        qw.lt("age",18);
        List<User> userList = userDao.selectList(qw);
        System.out.println(userList);
    }
- lt: 小于(<) ,最终的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (age < ?)

第二种:QueryWrapper的基础上使用lambda
    @Test
    void testGetByCon2(){
        QueryWrapper<User> qw = new QueryWrapper<User>();
        qw.lambda().lt(User::getAge, 18);//添加条件
        List<User> userList = userDao.selectList(qw);
        System.out.println(userList);
    }
- User::getAget,为lambda表达式中的,类名::方法名,最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
**注意:**构建LambdaQueryWrapper的时候泛型不能省。
第三种:LambdaQueryWrapper
    @Test
    void testGetByCon3(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 18);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
2.多条件查询
需求:查询数据库表中,年龄在10岁到30岁之间的用户信息
    @Test
    void testGetByCons(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 30).gt(User::getAge, 10);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
需求:查询数据库表中,年龄小于10或年龄大于30的数据
@Test
    void testGetAll(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

- or()就相当于我们sql语句中的or关键字,不加默认是and,最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
3.查询投影字段
查询投影即不查询所有字段,只查询出指定内容的数据。
    @Test
    void testGetSome(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.select(User::getId,User::getName,User::getAge);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

- select(…)方法用来设置查询的字段列,可以设置多个,最终的sql语句为:
SELECT id,name,age FROM user
- 如果使用的不是lambda,就需要手动指定字段
    @Test
    void testGetSome(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        lqw.select("id","name","age","tel");
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
4.聚合查询
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
    @Test
    void testGetByFunc(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        //lqw.select("count(*) as count");
        //SELECT count(*) as count FROM user
        //lqw.select("max(age) as maxAge");
        //SELECT max(age) as maxAge FROM user
        //lqw.select("min(age) as minAge");
        //SELECT min(age) as minAge FROM user
        //lqw.select("sum(age) as sumAge");
        //SELECT sum(age) as sumAge FROM user
        lqw.select("avg(age) as avgAge");
        //SELECT avg(age) as avgAge FROM user
        List<Map<String, Object>> userList = userDao.selectMaps(lqw);
        System.out.println(userList);
    }
5.分组查询
    @Test
    void testGetGroup(){
        QueryWrapper<User> lqw = new QueryWrapper<User>();
        lqw.select("count(*) as count,tel");
        lqw.groupBy("tel");
        List<Map<String, Object>> list = userDao.selectMaps(lqw);
        System.out.println(list);
    }
- groupBy为分组,最终的sql语句为
SELECT count(*) as count,tel FROM user GROUP BY tel

注意:
- 聚合与分组查询,无法使用lambda表达式来完成
- MP只是对MyBatis的增强,如果MP实现不了,我们可以直接在DAO接口中使用MyBatis的方式实现
6.等值查询
需求:根据用户名和密码查询用户信息
    @Test
    void testGetEqu(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
        User loginUser = userDao.selectOne(lqw);
        System.out.println(loginUser);
    }

- eq(): 相当于 =,对应的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
-  selectList:查询结果为多个或者单个 
-  selectOne:查询结果为单个 
7.范围查询
对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
- gt():大于(>)
- ge():大于等于(>=)
- lt():小于(<)
- lte():小于等于(<=)
- between():between ? and ?
    @Test
    void testGetByRange(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.between(User::getAge, 18, 30);
        //SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

8.模糊查询
查询表中name属性的值以
一开头的用户信息,使用like进行模糊查询
    @Test
    void testGetFuzzy(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
        lqw.likeLeft(User::getName, "一");
        //SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }

- like():前后加百分号,如 %一%
- likeLeft():前面加百分号,如 %一
- likeRight():后面加百分号,如 一%
9.排序查询
查询所有数据,然后按照id降序
    @Test
    void testGetSort() {
        LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
        /**
         * condition :条件,返回boolean,
         当condition为true,进行排序,如果为false,则不排序
         * isAsc:是否为升序,true为升序,false为降序
         * columns:需要操作的列
         */
        lwq.orderBy(true, false, User::getId);
        List<User> users = userDao.selectList(lwq.orderBy(true, false, User::getId));
        System.out.println(users);
    }

- orderBy排序 
  - condition:条件,true则添加排序,false则不添加排序
- isAsc:是否为升序,true升序,false降序
- columns:排序字段,可以有多个
 
- orderByAsc/Desc(单个column):按照指定字段进行升序/降序
- orderByAsc/Desc(多个column):按照多个字段进行升序/降序
- orderByAsc/Desc 
  - condition:条件,true添加排序,false不添加排序
- 多个columns:按照多个字段进行排序
 
除了上面介绍的这几种查询条件构建方法以外还会有很多其他的方法,比如isNull,isNotNull,in,notIn等等方法可供选择,具体参考官方文档
的条件构造器来学习使用,具体的网址为:
<条件构造器 | MyBatis-Plus (baomidou.com)>
10.映射匹配兼容性
前面我们已经能从表中查询出数据,并将数据封装到模型类中,这整个过程涉及到一张表和一个模型类:
 
之所以数据能够成功的从表中获取并封装到模型对象中,原因是表的字段列名和模型类的属性名一样。
1.表字段与编码属性设计不同步
当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?
MP给我们提供了一个注解@TableField,使用该注解可以实现模型类属性名和表的列名之间的映射关系
 
2.编码中添加了数据库中未定义的属性
当模型类中多了一个数据库表不存在的字段,就会导致生成的sql语句中在select的时候查询了数据库不存在的字段,程序运行就会报错,错误信息为:
Unknown column ‘多出来的字段名称’ in ‘field list’
具体的解决方案用到的还是@TableField注解,它有一个属性叫exist,设置该字段是否在数据库表中存在,如果设置为false则不存在,生成sql语句查询的时候,就不会再查询该字段了。
 
3.设置查询权限,对于有的敏感信息不返回前端
@TableField注解的一个属性叫select,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。
public class User {
    private Long id;
    private String name;
    @TableField(value="pwd",select=false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
}
4.表名与编码开发设计不同步
我们在查询时,其实并不是我们指定的查询表明,而是当我们设计好实体类时,在查询时,会默认把实体类名首字母变小写然后作为查询的表名,比如这里创建的User实体类,而我们的数据库表正好也是user,索引转换后刚好对应,就成功查询到啦,但是入过数据库表不是user呢?再转换就会出错,所以需要我们设置一个别名
 
 解决方案是使用MP提供的另外一个注解@TableName来设置表与模型类之间的对应关系。
 
四、DML编程控制
前面新增成功后,主键ID是一个很长串的内容,我们更想要的是按照数据库表字段进行自增长,在解决这个问题之前,我们先来分析下ID该如何选择:
不同的表应用不同的id生成策略
- 日志:自增(1,2,3,4,……)
- 购物订单:特殊规则(FQ23948AK3843)
- 外卖单:关联地区日期等信息(10 04 20200314 34 91)
- 关系表:可省略id
- …
MP中主键生成策略需要使用一个MP注解@TableId
1.id生成策略–AUTO
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(value="pwd",select=false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist=false)
    private Integer online;
}
因为之前生成主键ID的值比较长,会把MySQL的自动增长的值变的很大,所以需要将其调整为目前最新的id值。
在修改前先把这个自增勾去掉点一下保存然后再勾上,然后再整为目前最新的id值,如果不这样可能会出现修改了新的id值后插入仍然没用的情况
 
 
根据你数据表中的数据设置新的id值
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
2.id生成策略–INPUT
	@TableId(type = IdType.INPUT)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
把这里的自动递增去掉,然后保存设置
 
需要自己手动设置id,不设置会报错
    @Test
    void testSave() {
        User user = new User();
        user.setId(6L);
        user.setName("一只小可爱~");
        user.setPassword("!666");
        user.setAge(18);
        user.setTel("001");
        userDao.insert(user);
    }
3.id生成策略–ASSIGN_ID
	@TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @Test
    void testSave() {
        User user = new User();
        user.setName("一只小可爱~");
        user.setPassword("!666");
        user.setAge(18);
        user.setTel("001");
        userDao.insert(user);
    }
**注意:**这种生成策略,不需要手动设置ID,如果手动设置ID,则会使用自己设置的值。生成的ID就是一个Long类型的数据。
 
4.id生成策略–ASSIGN_UUID
使用uuid需要注意的是,主键的类型不能是Long,而应该改成String类型,否则报错,注意实体类中也要做出修改!
 
所以需要修改id为varchar类型的,并且长度要大于32,因为UUID生成的主键为32位,如果长度小的话就会导致插入失败。
 
	@TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @Test
    void testSave() {
        User user = new User();
        user.setName("一只小可爱~");
        user.setPassword("!666");
        user.setAge(18);
        user.setTel("001");
        userDao.insert(user);
    }

5.ID生成策略对比
介绍了这些主键ID的生成策略,我们以后该用哪个呢?
- NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂
- AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用
- ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢
- ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键
- 综上所述,每一种主键策略都有自己的优缺点,根据自己项目业务的实际情况来选择使用才是最明智的选择。
五、多记录操作
1.批量删除
nt deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    @Test
    void testDeleteByIds(){
        //删除指定多条数据
        List<Long> list = new ArrayList<>();
        list.add(3L);
        list.add(4L);
        userDao.deleteBatchIds(list);
    }

2.逻辑删除
对于删除操作业务问题来说有:
- 物理删除:业务数据从数据库中丢弃,执行的是delete操作
- 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是update操作
MP中逻辑删除具体该如何实现?
1.修改数据库表添加deleted列
 
字段名可以任意,内容也可以自定义,比如0代表正常,1代表删除,可以在添加列的同时设置其默认值为0正常
 
2.实体类添加属性
标识新增的字段为逻辑删除字段,使用@TableLogic
private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @TableLogic(value = "0", delval = "1")
    private Integer deleted;
3.运行测试方法
    @Test
    void testDeleteById(){
        userDao.deleteById(1L);
    }

-  MP的逻辑删除会将所有的查询都添加一个未被删除的条件,也就是已经被删除的数据是不应该被查询出来的。 
-  如果还是想把已经删除的数据都查询出来该如何实现呢? 
这就需要我们手动书写查询sql了
@Mapper
public interface UserDao extends BaseMapper<User> {
    //查询所有数据包含已经被删除的数据
    @Select("select * from user")
    public List<User> selectAll();
}	
    @Test
    void testGetAll() {
List<User> users = userDao.selectAll();
        System.out.println(users);
    }

六、乐观锁
乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新。
乐观锁的实现方式:
- 数据库表中添加version列,比如默认值给1
- 第一个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
- 第二个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
- 第一个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 第二个线程执行更新时,set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 假如这两个线程都来更新数据,第一个和第二个线程都可能先执行
- 假如第一个线程先执行更新,会把version改为2,
- 第二个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第二个线程会修改失败
- 假如第二个线程先执行更新,会把version改为2,
- 第一个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第一个线程会修改失败
- 不管谁先执行都会确保只能有一个线程更新数据,这就是MP提供的乐观锁的实现原理分析。
具体的实现步骤如下:
1.数据库表添加列
列名可以任意,比如使用version,给列设置默认值为1
 
2.在模型类中添加对应的属性
根据添加的字段列名,在模型类中添加对应的属性值
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
    @TableLogic(value = "0", delval = "1")
    private Integer deleted;
    @Version
    private Integer version;
3.添加乐观锁的拦截器
@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        //1.定义Mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        //2.添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mpInterceptor;
    }
}
4.执行更新操作
要想实现乐观锁,首先第一步应该是拿到表中的version,然后拿version当条件在将version加1更新回到数据库表中,所以我们在查询的时候,需要对其进行查询
    @Test
    void testUpdate() {
        User user = userDao.selectById(2L);
        user.setName("mumu");
        user.setPassword("666");
        userDao.updateById(user);
    }

ps:模拟一下加锁的情况
看看能不能实现多个人修改同一个数据的时候,只能有一个人修改成功。
    @Test
    void testUpdate() {
        User user = userDao.selectById(2L);
        User user1 = userDao.selectById(2L);
        
        user.setName("mumu");
        user.setPassword("666");
        userDao.updateById(user);
        
        user1.setName("mumu");
        user1.setPassword("666999");
        userDao.updateById(user1);
    }
运行前:
 
运行后:
 
我们发现这个时候只执行了user的修改,而user1的并没成功修改,说明乐观锁实现了
七、结语
感谢各位大佬观看,如有问题可以评论私信哦,互相学习~谢谢




![[附源码]Python计算机毕业设计Django停车场管理系统](https://img-blog.csdnimg.cn/df62cacc4c64437db2c5d3cb9bf20d97.png)













