MyBatis-Plus 扩展
1.逻辑删除
逻辑删除:假删除、将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录。
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据。
当删除数据的时候,自定修改此列的属性值 1:逻辑删除 0:未逻辑删除
当查询数据的时候,默认只查询 deleted =0 ,即逻辑未删除的数据
逻辑删除实现
- 数据库 添加一个表字段deleted
ALTER TABLE USER ADD deleted INT DEFAULT 0 ; # int 类型 1 逻辑删除 0 未逻辑删除
- 实体类 添加逻辑删除属性
单一指定
@TableLogic
private Integer deleted;
yml配置类 全局指定
mybatis-plus:
global-config:
db-config:
#逻辑删除的属性
logic-delete-field: deleted #字段名
# #默认 可改
# logic-delete-value: 0 # 删除
# logic-not-delete-value: 1 #未删除
测试
@SpringBootTest
public class MyBatisPlusTableLogicTest {
@Autowired
private UserMapper userMapper;
@Test
public void test1(){
//delete from user where id=1 物理删除
//-> update user set daleted=1 where id=1 and deleted=0
userMapper.deleteById(1);
}
}
==> Preparing: UPDATE t_user SET deleted=1 WHERE id=? AND deleted=0
2.乐观锁实现
2.1 乐观锁和悲观锁
乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制!!
悲观锁:
悲观锁的基本思想是,在整个数据访问过程中,将共享资源锁定,以确保其他线程或进程不能同时访问和修改该资源。悲观锁的核心思想是"先保护,再修改"。在悲观锁的应用中,线程在访问共享资源之前会获取到锁,并在整个操作过程中保持锁的状态,阻塞其他线程的访问。只有当前线程完成操作后,才会释放锁,让其他线程继续操作资源。这种锁机制可以确保资源独占性和数据的一致性,但是在高并发环境下,悲观锁的效率相对较低。
乐观锁:
乐观锁的基本思想是,认为并发冲突的概率较低,因此不需要提前加锁,而是在数据更新阶段进行冲突检测和处理。乐观锁的核心思想是"先修改,后校验"。在乐观锁的应用中,线程在读取共享资源时不会加锁,而是记录特定的版本信息。当线程准备更新资源时,会先检查该资源的版本信息是否与之前读取的版本信息一致,如果一致则执行更新操作,否则说明有其他线程修改了该资源,需要进行相应的冲突处理。乐观锁通过避免加锁操作,提高了系统的并发性能和吞吐量,但是在并发冲突较为频繁的情况下,乐观锁会导致较多的冲突处理和重试操作。
注:悲观锁和乐观锁是两种解决并发数据问题的思路,不是具体技术!!!
具体技术和方案:
- 乐观锁实现方案和技术:
- 版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,若一致则更新成功,否则表示数据已被修改,需要进行冲突处理。
- CAS(Compare-and-Swap):使用原子操作比较当前值与旧值是否一致,若一致则进行更新操作,否则重新尝试。
- 无锁数据结构:采用无锁数据结构,如无锁队列、无锁哈希表等,通过使用原子操作实现并发安全。
- 悲观锁实现方案和技术:
- 锁机制:使用传统的锁机制,如互斥锁(Mutex Lock)或读写锁(Read-Write Lock)来保证对共享资源的独占访问。
- 数据库锁:在数据库层面使用行级锁或表级锁来控制并发访问。
- 信号量(Semaphore):使用信号量来限制对资源的并发访问。
2.2 乐观锁保证数据一致性:版本号判断
实现流程:
- 每条数据添加一个版本号字段version
- 取出数据时,获取当前version 【防止并发数据更新时出现错误数据】
- 更新时,再次检查 获取的version与数据库最新version是否一致
- 如果version一致,证明没有人修改数据,再执行更新,set 数据 ,version+1
- 如果version不一致,证明该数据已经被修改,取出的数据已经失效,即更新失败
2.3 使用 MyBatis-Plus 数据使用乐观锁
步骤:
- 添加版本号更新插件,使用拦截器,完成版本对比,版本自增+1
@Bean
public MybatisPlusInterceptor plusInterceptor(){
//todo:mybatis-plus的插件集合[将所需插件加入到这个集合中即可,如分页插件、乐观锁插件..]
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//分页插件
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//乐观锁【版本号插件】 mybatis-plus会在更新时自动对比版本号字段和 version+1
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
- 数据库添加字段version , 实体类添加@Version private Integer version
ALTER TABLE t_user ADD VERSION INT DEFAULT 1 ; # int 类型 乐观锁字段
//todo:版本号字段
@Version
private Integer version;
- 正常更新使用
//演示乐观锁生效场景
@Test
public void test1(){
//步骤1: 先查询,在更新 获取version数据
//同时查询两条,但是version唯一,最后更新的失败
User user = userMapper.selectById(5); // version=1
User user1 = userMapper.selectById(5); // version=1
user.setAge(20);
user1.setAge(30);
userMapper.updateById(user); // 20 v=1 检查 v=1 version一致 执行更新 + version+1=2
//乐观锁生效,失败!
userMapper.updateById(user1); // 获取的version=1,检查version=2,1!=2 失效数据,修改失败
}
3.防止全表更新和删除实现
针对 update 和 delete 语句 作用: 阻止恶意的全表更新删除
添加防止全表更新和删除拦截器
@Bean
public MybatisPlusInterceptor plusInterceptor(){
//todo:mybatis-plus的插件集合[将所需插件加入到这个集合中即可,如分页插件、乐观锁插件..]
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//分页插件
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//乐观锁【版本号插件】 mybatis-plus会在更新时自动对比版本号字段和 version+1
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
//防止全表删除和更新拦截器
mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return mybatisPlusInterceptor;
}
@Test
public void testDelete(){
//全表删除 拦截报错
userMapper.delete(null);
// Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
//全表更新 拦截报错
User user = new User();
user.setName("xxxxx");
user.setEmail("xxxx@qq.com");
userMapper.update(user,null);
//Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
}