1. MybatisPlus是什么
MybatisPlus,简称MP,是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变。MP为简化开发、提高效率而生。它已经封装好了单表curd方法,我们直接调用这些方法就能实现单表CURD。
注意:MP仅仅提供了单表的CURD。如果要实现多表联查,就只能自己动手实现了,按Mybatis的方式写
2. 快速入门
2.1 使用步骤
- 导入MybatisPlus的依赖。
- 创建Mapper接口继承
BaseMapper<实体类>,接口上添加注解@Mapper
2.2 使用测试
-
添加依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> -
创建Mapper接口继承BaseMapper<实体类>
@Mapper public interface UserMapper extends BaseMapper<User> { } -
创建测试类进行测试
@SpringBootTest public class MpTest { @Autowired private UserMapper userMapper; @Test public void test1(){ User user = userMapper.selectById(1); System.out.println(user); } }
3. MybatisPlus的实体类注解
MybatisPlus根据BaseMapper<实体类>中的实体类的信息来推断出表的信息,从而生成SQL默认情况下:
- 实体类的类名驼峰转下划线作为表名
- 实体类里的属性
- 属性:与字段必须一一对应。每个属性都必须有一个对应的字段
- 命名:所有属性名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
- 把名为id的字段作为主键
如果实体类确实不符合以上规则,MybatisPlus提供了一些注解用于特殊设置:
-
表名与实体类名的映射:
@TableName:用于设置 实体类名 与 数据库表名 的对应关系。当表名和实体类名不对应时需要设置 -
字段与属性的映射:
@TableField:用于非主键字段。设置非主键字段与属性对应关系。当表字段名与属性名不对应时设置@TableId:用于主键字段。设置主键字段与属性对应关系,还可以设置主键生成策略
3.1 @TableName
使用场景:用于解决实体类名与数据库表名不符合下划线与驼峰命名的映射规则的问题
解决方案:在实体类上添加注解@TableName("表名称")
3.2 @TableField
使用场景:
- 属性名与数据库表字段名不一致
- 属性名不是数据库表字段
- 不想查询对应的字段信息,比如敏感信息密码。
解决方案:在非主键字段上添加注解@TableField("表字段名")
3.3 @TableId
说明:当我们调用了Mapper的insert方法,插入数据时,并不需要指定id值,MP会按照既定的主键生成策略帮我们生成好主键值。
使用方法:修改实体类,在主键对应的属性上添加注解@TableId(type=主键策略)
主键自增策略:
-
IdType.NONE:不设置,跟随全局 -
IdType.AUTO:主键值自增,前提是主键支持自增 -
IdType.INPUT:由我们的代码设置主键值,不让MybatisPlus生成主键值 -
IdType.ASSIGN_ID:由MybatisPlus使用雪花算法生成一个id值,可兼容数值型(long类型)和字符串型 -
IdType.ASSIGN_UUID:由MybatisPlus使用UUID算法生成一个id值。不推荐,因为UUID值是乱序的,会影响主键字段上的索引
示例:
4. MybatisPlus简单的CURD
MybatisPlus内置了强大的BaseMapper,它已经提供好了单表CURD功能:只要我们的Mapper接口继承了BaseMapper,就可以直接使用整套的单表CURD功能了。
提供的方法如下:
实例演示
@SpringBootTest
public class MpCurdTest {
@Autowired
private UserMapper userMapper;
/**
* 查询数据的方法示例
*/
@Test
public void testSelect(){
//1. 根据id查询一个
User user = userMapper.selectById(1);
System.out.println(user);
//2. 查询列表(查询全部)
List<User> userList = userMapper.selectList(null);
for (User u : userList) {
System.out.println(u);
}
//3. 查询数量
Integer count = userMapper.selectCount(null);
System.out.println("总数量:" + count);
}
/**
* 插入数据的示例
*/
@Test
public void testInsert(){
User user = new User();
user.setUname("老王");
user.setAge(40);
user.setPassword("heihei");
user.setTel("13800138000");
user.setSex("男");
System.out.println("插入之前的user没有id:" + user);
int i = userMapper.insert(user);
System.out.println("影响的行数是:" + i);
System.out.println("插入之后的user有id了:" + user);
}
/**
* 修改数据的示例
*/
@Test
public void testUpdate(){
User user = new User();
user.setId(1L);
user.setPassword("666666");
//注意:方法是根据id修改的,所以传入的对象里必须有主键id的值
int i = userMapper.updateById(user);
System.out.println("影响的行数是:" + i);
}
/**
* 删除数据的示例
*/
@Test
public void testDelete(){
//根据id删
int i = userMapper.deleteById(1);
System.out.println("影响的行数是:" + i);
//根据id批量删
int count = userMapper.deleteBatchIds(List.of(9, 10, 11, 12, 14, 15, 16, 17));
System.out.println("影响行数:"+count);
}
}
5. 条件查询
除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件。
5.1 QueryWrapper的使用
QueryWrapper的用法:
//直接new的方式。
QueryWrapper<实体类> wrapper = new QueryWrapper<实体类>()
.select("字段1,字段2,字段3 as 别名, ifnull(...) as xx")
//条件方法:eq, ne, gt, ge, lt, le, like, notLike, in, notIn, isNull, isNotNull....
.条件方法(字段名,值)
.条件方法(是否要拼接此条件, 字段名,值)
.orderByAsc("字段名...").orderByDesc("字段名...")
//直接Wrappers的静态方法方式。
QueryWrapper<实体类> wrapper = Wrappers.<实体类>query()
.select("字段1,字段2,字段3 as 别名, ifnull(...) as xx")
//条件方法:eq, ne, gt, ge, lt, le, like, notLike, in, notIn, isNull, isNotNull....
.条件方法(字段名,值)
.条件方法(是否要拼接此条件, 字段名,值)
.orderByAsc("字段名...").orderByDesc("字段名...")
使用入门示例
@SpringBootTest
public class Demo04QueryTest {
@Autowired
private UserMapper userMapper;
/**
* 查询所有年龄大于20岁的用户
* 只查询id、姓名、年龄
* 结果按年龄降序排列
* select id,user_name,age from tb_user where age>20 order by age desc;
*/
@Test
public void test1(){
//创建一个QueryWrapper对象,泛型是实体类
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 设置查询条件:age > 20
wrapper.select("id", "user_name", "age")
.gt("age", 20)
.orderByDesc("age");
//调用selectList方法,根据条件查询列表
List<User> users = userMapper.selectList(wrapper);
//3.打印输出
users.forEach(System.out::println);
}
}
设置查询条件
如果要使用QueryWrapper进行条件查询,常用的查询条件方法有:
| 查询方法 | 说明 | 备注 |
|---|---|---|
eq(字段名,值) | 等于= | 字段名=值 条件 |
ne(字段名,值) | 不等与<> | |
gt(字段名,值) | 大于> | |
ge(字段名,值) | 大于等于>= | |
lt(字段名,值) | 小于< | |
le(字段名,值) | 小于等于<= | |
like(字段名,值) | 模糊查询 LIKE | MybatisPlus会自动加上% |
notLike(字段名,值) | 模糊查询 NOT LIKE | MybatisPlus会自动加上% |
in(字段名,值集合/数组) | IN 查询 | |
notIn(字段名,值集合/数组) | NOT IN 查询 | |
isNull(字段名) | NULL 值查询 | |
isNotNull(字段名) | IS NOT NULL |
拓展了解:or连接条件
在QueryWrapper的条件连接中,所有查询条件默认都是使用and连接的。如果想要多个条件使用or,可以使用它的
or()方法注意:SQL语句中
or的优先级低于and示例:
@Test public void test3(){ QueryWrapper<AppUser> wrapper = new QueryWrapper<>(); // user_name = ? AND password = ? OR age > ? wrapper.eq("user_name", "jack").eq("password", "123456") .or() .gt("age", 15); List<AppUser> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
动态条件拼接
在实际的数据查询中,通常可能有多个条件组合查询,并且条件的数量是动态变化的。用户可以选择任意1个、2个、多个条件进行组合查询。如果用户没有选择某个条件,那么SQL语句中就不能添加这个查询条件,这就需要使用SQL语句的动态拼接了
MP的所有查询条件的方法都有重载:
| 查询方法 | 说明 | 备注 |
|---|---|---|
eq(条件, 字段名,值) | 当条件为true时才生效,等于= | 字段名=值 条件 |
ne(条件,字段名,值) | 当条件为true时才生效,不等与<> | |
gt(条件,字段名,值) | 当条件为true时才生效,大于> | |
ge(条件,字段名,值) | 当条件为true时才生效,大于等于>= | |
lt(条件,字段名,值) | 当条件为true时才生效,小于< | |
le(条件,字段名,值) | 当条件为true时才生效,小于等于<= | |
like(条件,字段名,值) | 当条件为true时才生效,模糊查询 LIKE | MP会自动加上% |
notLike(条件,字段名,值) | 当条件为true时才生效,模糊查询 NOT LIKE | MP会自动加上% |
in(条件,字段名,值集合/数组) | 当条件为true时才生效,IN 查询 | |
notIn(条件,字段名,值集合/数组) | 当条件为true时才生效,NOT IN 查询 | |
isNull(条件,字段名) | 当条件为true时才生效,NULL 值查询 | |
isNotNull(条件,字段名) | 当条件为true时才生效,IS NOT NULL |
示例:
@Test
public void test6(){
//模拟:客户端提交过来的查询条件
String uname = "a";
Integer age = 18;
String sex = null;
//1. 创建wrapper对象,构建查询条件
QueryWrapper<User> wrapper = Wrappers.<User>query()
.select("id,user_name as uname,age,tel")
//动态拼接查询条件: 如果age不为空,才会拼接
.gt(age != null,"age", age)
.like(uname!=null && !"".equals(uname),"user_name", uname)
.eq(sex!=null&&!"".equals(sex),"sex",sex);
//2.查询数据
List<User> users = userMapper.selectList(wrapper);
//3.打印输出
users.forEach(System.out::println);
}
5.2 LambdaQueryWrapper的使用
使用QueryWrapper进行条件操作时,所有的条件都写的是**数据库的字段名**,这种方式很容易写错,而且不方便。所以MP提供了LambdaQueryWrapper解决这个问题。
使用LambdaQueryWrapper封装条件时,所有的条件不再写数据库字段,而是写字段对应的属性。
LambdaQueryWrapper的用法:
//直接new
LambdaQueryWrapper<实体类> wrapper = new LambdaQueryWrapper<实体类>()
.select(实体类::get属性, ....)
.条件方法(实体类::get属性,值)
.条件方法(是否要拼接此条件, 实体类::get属性,值)
.orderByAsc(实体类::get属性,..).orderByDesc(实体类::get属性,...)
//使用Wrappers的静态方法
LambdaQueryWrapper<实体类> wrapper = Wrappers.<实体类>lambdaQuery()
.select(实体类::get属性, ....)
.条件方法(实体类::get属性,值)
.条件方法(是否要拼接此条件, 实体类::get属性,值)
.orderByAsc(实体类::get属性,..).orderByDesc(实体类::get属性,...)
使用入门示例
@SpringBootTest
public class Demo05LambdaQueryTest {
@Autowired
private UserMapper userMapper;
@Test
public void test01(){
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.select(User::getId, User::getUserName, User::getAge)
.gt(User::getAge, 20)
.orderByDesc(User::getAge);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
}
设置查询条件
如果要使用LambdaQueryWrapper进行条件查询,常用的查询条件方法有:
| 查询方法 | 说明 | 备注 |
|---|---|---|
eq(JavaBean属性,值) | 等于= | 字段名=值 条件 |
ne(JavaBean属性,值) | 不等与<> | |
gt(JavaBean属性,值) | 大于> | |
ge(JavaBean属性,值) | 大于等于>= | |
lt(JavaBean属性,值) | 小于< | |
le(JavaBean属性,值) | 小于等于<= | |
like(JavaBean属性,值) | 模糊查询 LIKE | MybatisPlus会自动加上% |
notLike(JavaBean属性,值) | 模糊查询 NOT LIKE | MybatisPlus会自动加上% |
in(JavaBean属性,值集合/数组) | IN 查询 | |
notIn(JavaBean属性,值集合/数组) | NOT IN 查询 | |
isNull(JavaBean属性) | NULL 值查询 | |
isNotNull(JavaBean属性) | IS NOT NULL |
示例
@Test
public void test02(){
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 15).lt(User::getAge, 25);
List<User> users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
动态条件拼接
MP的所有查询条件的方法都有重载:
| 查询方法 | 说明 | 备注 |
|---|---|---|
eq(条件, JavaBean的属性,值) | 当条件为true时才生效,等于= | 字段名=值 条件 |
ne(条件,JavaBean的属性,值) | 当条件为true时才生效,不等与<> | |
gt(条件,JavaBean的属性,值) | 当条件为true时才生效,大于> | |
ge(条件,JavaBean的属性,值) | 当条件为true时才生效,大于等于>= | |
lt(条件,JavaBean的属性,值) | 当条件为true时才生效,小于< | |
le(条件,JavaBean的属性,值) | 当条件为true时才生效,小于等于<= | |
like(条件,JavaBean的属性,值) | 当条件为true时才生效,模糊查询 LIKE | MP会自动加上% |
notLike(条件,JavaBean的属性,值) | 当条件为true时才生效,模糊查询 NOT LIKE | MP会自动加上% |
in(条件,JavaBean的属性,值集合/数组) | 当条件为true时才生效,IN 查询 | |
notIn(条件,JavaBean的属性,值集合/数组) | 当条件为true时才生效,NOT IN 查询 | |
isNull(条件,JavaBean的属性) | 当条件为true时才生效,NULL 值查询 | |
isNotNull(条件,JavaBean的属性) | 当条件为true时才生效,IS NOT NULL |
示例
@Test
public void test04(){
//模拟:客户端提交过来的两个查询条件,最小年龄minAge,最大年龄maxAge
Integer minAge = 15;
Integer maxAge = null;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
//动态拼接查询条件: 如果minAge不为空,才会拼接 age > minAge值
wrapper.gt(minAge!=null, User::getAge, minAge)
// 如果maxAge不为空,才会拼接 age < maxAge值
.lt(maxAge!=null, User::getAge, maxAge);
List<User> users = userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
6. MybatisPlus分页查询
6.1 介绍
之前我们要实现分页查询,要么在mybatis中引入pageHelper插件,要么完全手动实现分页查询。
而MybatisPlus本身就内置分页插件,直接配置即可,不需要再额外导入任何插件,也不需要我们再手动实现了。
6.2 使用步骤
- 配置分页插件:创建一个配置类,在配置类中增加MybatisPlus的分页插件
- 实现分页查询:调用Mapper的selectPage方法实现分页查询功能
6.3 分页API
MP的Mapper提供的分页查询方法是:IPage selectPage(IPage page, Wrapper wrapper)
-
参数page:用于封装分页条件,包括页码和查询数量
-
参数wrapper:用于封装查询条件,实现条件查询并分页
-
返回值Page:分页查询的结果
IPage:是一个接口;Page是它的实现类;是分页信息对象
-
在执行分页查询前,把分页参数封装成Page对象
-
当执行分页查询后,MP会把查询结果封装到这个Page对象中
-
常用方法有
| 方法 | 说明 |
|---|---|
new Page(pageNumber, pageSize) | 创建分页信息Page对象 |
getCurrent() | 获取当前页码 |
getPages() | 获取总页数 |
getSize() | 获取每页几条 |
getTotal() | 获取总数量 |
getRecords() | 获取数据列表 |
6.4 示例
配置分页插件
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建拦截器对象MybatisPlusInterceptor
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件PaginationInnerInterceptor,注意数据库的类型。如果数据库是MySQL,就设置DbType.MYSQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
执行分页查询
@SpringBootTest
public class Demo06PageTest {
@Autowired
private UserMapper userMapper;
@Test
public void testPage(){
//准备查询条件:性别为男的
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getSex, "男");
//执行分页查询
Page<User> page = userMapper.selectPage(new Page<>(1, 3), wrapper);
//得到分页查询结果
System.out.println("总数量:" + page.getTotal());
System.out.println("总页数:" + page.getPages());
System.out.println("每页几条:" + page.getSize());
System.out.println("当前页码:" + page.getCurrent());
List<User> list = page.getRecords();
list.forEach(System.out::println);
}
}
7. 自动填充功能
7.1 介绍
之前我们要实现公共字段填充,是使用AOP思想进行填充。
而MybatisPlus本身就提供了自动填充,直接配置即可,不需要再额外导入任何插件,也不需要我们再手动实现了。
7.2 使用步骤
- 在需要自动填充的字段上增加注解 @TableField(value = “字段名”, fill = FieldFill.INSERT) 表示插入的时候进行填充 @TableField(value = “字段名”, fill = FieldFill.UPDATE) 表示修改的时候进行填充
- 自定义公共填充类实现元对象处理器接口
7.3 示例
在需要自动填充的字段上增加注解
@TableField(fill = FieldFill.INSERT) //插入时填充字段
@ApiModelProperty(value = "创建时间")
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)//插入和修改时填充字段
@ApiModelProperty(value = "更新时间")
private LocalDateTime updateTime;
自定义公共填充类实现元对象处理器接口
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
// 或者
this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
// 或者
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
}
8. MybatisPlus的逻辑删除
8.1 什么是逻辑删除
如果需要删除一条数据,开发中往往有两种方案可以实现:
- 物理删除:真正的从数据库中把数据删除掉
- 逻辑删除:有一个字段用于标识数据是否删除的状态。删除时仅仅是把字段设置为“已删除”状态,数据还在数据库里,并非真正的删除
在实际开发中,逻辑删除使用的更多一些。所以MP也提供了逻辑删除的支持,帮助我们更方便的实现逻辑删除
8.2 MP的逻辑删除用法
使用步骤:
- 修改数据库表,增加一个字段
deleted。字段名称可以随意,仅仅是用于存储数据的状态的- 修改实体类,增加对应的属性,并在属性上添加注解
@TableLogic- 修改配置文件
application.yaml,声明删除与未删除的字面值
MP逻辑删除的本质是:
- 当执行删除时,MP实际上执行的是update操作,把状态字段修改为“已删除状态”
- 当执行查询时,MP会帮我们加上一个条件
状态字段 = 未删除,从而只查询未删除的数据
8.3 示例
增加状态字段
alter table user add deleted int default 0;
增加属性并加@TableLogic注解
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private String password;
private Integer age;
private String tel;
private String sex;
//增加@TableLogic注解,这个字段被声明为 逻辑删除状态字段
@TableLogic
private int deleted;
}
修改配置文件
mybatis-plus:
global-config:
db-config:
#logic-delete-field: deleted #全局的默认逻辑删除字段名,即 状态字段名。
logic-delete-value: 1 #已删除状态的值
logic-not-delete-value: 0 #未删除状态的值
测试
@Test
public void testLogicDelete(){
//删除id为5的数据
userMapper.deleteById(5L);
//查询所有数据,查询结果中没有id为5的数据。但是数据库里id为5的数据还在,只是deleted为1(已删除状态)
List<User> users = userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
9. 乐观锁
9.1 说明
-
在自己操作资源数据期间,其它线程很少来干扰,所以并不需要真正添加锁,可以有更好的性能
自己在每次操作数据时,都先校验感知一下,数据是否被其它线程修改过了
如果被其它线程修改过了:就放弃操作或者报错
如果没有被其它线程修改:就直接执行操作
-
特点:
- 安全性足够
- 性能比较好。因为没有真正加锁,在当前线程操作期间,可以有其它线程执行读操作,不会有任何影响
-
适合:读多写少的情况
-
思想:
- CAS思想:Compare And Swap 对比并交换设置。比如:Java里的AtomicInteger底层使用了CAS思想,并不需要真正加锁,也能实现多线程操作时的线程安全性
- 版本号:给数据设置版本号,每次变动数据时都要把版本号+1。修改数据就可以感知,数据是否被其它线程修改了
MP的乐观锁:采用的是版本号方式
9.2 使用步骤
- 修改数据库表,增加一个字段
version。字段名称随意,仅仅是用于记录数据版本号- 修改实体类,增加对应的属性,并在属性上增加注解
@Version- 创建配置类,在配置类里设置好乐观锁插件
- 修改数据时乐观锁就生效了:但是要先查询数据,再修改数据,乐观锁才会生效;直接修改数据是不生效的
9.3 版本号实现方式
先查询数据,得到这条数据的version版本号。假如是0
在修改数据时,带上这个version版本号
MP执行update时,会加上
set version = version + 1 where version = 这个version版本号如果数据库里的数据的version,和带上的这个version不一致,就放弃修改
9.4 示例
增加版本号字段
alter table user add version int default 0;
增加属性并加@Version注解
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private String password;
private Integer age;
private String tel;
private String sex;
@TableLogic
private int deleted;
//增加对应的属性,并添加@Version注解
@Version
private int version;
}
配置乐观锁插件
在配置类(MpConfig)里,并在配置类里配置好乐观锁插件
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建拦截器对象MybatisPlusInterceptor
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件PaginationInnerInterceptor,注意数据库的类型。如果数据库是MySQL,就设置DbType.MYSQL
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//添加乐观锁插件OptimisticLockerInnerInterceptor
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
测试
@Test
public void testLock(){
//先查询数据。假如查询到的version版本号是0
User user = userMapper.selectById(6L);
System.out.println(user);
user.setAge(19);
//再更新数据。MP会带上版本号0,
// 如果数据库里的id为6的数据版本号也是0,就能修改成功,然后版本号+1变成1
// 如果数据库里的id为6的数据版本号不是0,就放弃修改(MP会认为数据被其它线程修改了)
userMapper.updateById(user);
}
10. 悲观锁
-
在自己操作资源数据期间,认为总有其它线程来干扰,所以要添加排他性的、独占性的锁。
在我加锁操作期间,其它所有线程都要阻塞排队。直到我释放锁,其他线程才可以再抢锁操作
-
特点:
- 安全性高,因为把多线程并发变成了串行
- 性能较低,因为同一时间只有一个线程能操作,其它线程都是阻塞排队状态
-
适用:写多读少的情况
-
技术:
- synchronized关键字
- Lock对象