三、MybatisPlus高级扩展
1、逻辑删除
1、概念
1、可以方便地实现对数据库记录的逻辑删除而不是物理删除。
2、逻辑删除是指通过更改记录的状态或添加标记字段来模拟删除操作,从而保留了删除前的数据,便于后续的数据分析和恢复。
1、比较逻辑删除和物理删除
1、物理删除
- 可以方便地实现对数据库记录的逻辑删除而不是物理删除。逻辑删除是指
2、逻辑删除
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
2、实现
1、步骤
1、在数据库和实体类都添加代表被删除的字段。到时候查询就方便了
2、用@TableLogic注解指定一个逻辑删除的字段【MybatisPlus会做好其他工作】
3、正常编写代码
2、例子
1、数据库准备deleter字段
ALTER TABLE USER ADD deleted INT DEFAULT 0 ; # int 类型 1 逻辑删除 0 未逻辑删除
2、java实体类准备deleter字段
package com.atguigu.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
/**
* @TableName("user")这个注解可以不加
* 直接使用实体类的名字作为数据库的表名
* 忽略大小写
* 当数据库的表名与实体类命名不同(忽略大小写)使用@TableName()来指定表名
* 这个是单个修改,万一有很多个实体类需要修改成t_开头呢。
*/
@TableName("user")
@Data
public class User {
// 从单一表设置,数据主键自增长
@TableId(type = IdType.AUTO )
private Integer id;
// 如果数据库没有这个字段、或者字段名称和实体类名称不同,我们就可以添加这个注解
@TableField(value = "name")
private String name;
private Integer age;
private String email;
/*
当前属性对应的列就是逻辑删除的字段(属性)
当删除的时候属性值就默认为0,不删除就是1
逻辑删除:本质就是修改操作
*/
@TableLogic
private Integer deleter;
@Version
private Integer version;
}
3、在实体类制定单一字段
/*
当前属性对应的列就是逻辑删除的字段(属性)
当删除的时候属性值就默认为0,不删除就是1
逻辑删除:本质就是修改操作
*/
@TableLogic
private Integer deleter;
4、也可以在application.yaml中指定配置类的deleter字段
# 连接池配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql:///manager
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
type-aliases-package: com.atguigu.pojo
global-config:
db-config:
table-prefix: t_ #数据库表名的前缀 不用在每个实体类配置
id-type: assign_id #这个是设置每个表都这样,全局设置
#设置逻辑删除字段
logic-delete-field: deleter #逻辑删除的属性
logic-delete-value: 0 #表示已经逻辑删除了
logic-not-delete-value: 1 #表示没有逻辑删除
5、测试代码
package com.atguigu;
import com.atguigu.mapper.UserMapper;
import com.atguigu.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class MyBatisPlusTableLogicTest {
@Autowired
private UserMapper userMapper;
@Test
public void testLogicDelete() {
userMapper.deleteById(1);
userMapper.selectList(null);
}
}
3、总结
1、里面涉及到了@TableLogic
2、@TableLogic
- 当前属性对应的列就是逻辑删除的字段(属性)
- 当删除的时候属性值就默认为0,不删除就是1
- 逻辑删除:本质就是修改操作
3、是一种思路
2、乐观锁
1、介绍
1、需求

1、并发可能会产生脏读。
2、本质是数据不同步
2、思路
乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制!!
3、乐观锁
1、乐观锁的基本思想是,认为并发冲突的概率较低,因此不需要提前加锁,而是在数据更新阶段进行冲突检测和处理。
2、乐观锁的核心思想是"先修改,后校验"。在乐观锁的应用中,线程在读取共享资源时不会加锁,而是记录特定的版本信息。
3、当线程准备更新资源时,会先检查该资源的版本信息是否与之前读取的版本信息一致,如果一致则执行更新操作,否则说明有其他线程修改了该资源,需要进行相应的冲突处理。乐观锁通过避免加锁操作,提高了系统的并发性能和吞吐量,但是在并发冲突较为频繁的情况下,乐观锁会导致较多的冲突处理和重试操作。
4、类似操作系统中的同步
4、悲观锁
1、悲观锁的基本思想是,在整个数据访问过程中,将共享资源锁定,以确保其他线程或进程不能同时访问和修改该资源。
2、悲观锁的核心思想是"先保护,再修改"。在悲观锁的应用中,线程在访问共享资源之前会获取到锁,并在整个操作过程中保持锁的状态,阻塞其他线程的访问。
3、只有当前线程完成操作后,才会释放锁,让其他线程继续操作资源。这种锁机制可以确保资源独占性和数据的一致性,但是在高并发环境下,悲观锁的效率相对较低。
4、类似操作系统中的互斥
5、总结
1、它们两个是思想。不是技术
2、具体技术实现
1、乐观锁实现方案和技术:
-
版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,若一致则更新成功,否则表示数据已被修改,需要进行冲突处理。
-
CAS(Compare-and-Swap):使用原子操作比较当前值与旧值是否一致,若一致则进行更新操作,否则重新尝试。
-
无锁数据结构:采用无锁数据结构,如无锁队列、无锁哈希表等,通过使用原子操作实现并发安全。
2、悲观锁实现方案和技术:
- 锁机制:使用传统的锁机制,如互斥锁(Mutex Lock)或读写锁(Read-Write Lock)来保证对共享资源的独占访问。
- 数据库锁:在数据库层面使用行级锁或表级锁来控制并发访问。
- 信号量(Semaphore):使用信号量来限制对资源的并发访问。
3、实例
1、实现流程
- 每条数据添加一个版本号字段version
- 取出记录时,获取当前 version
- 更新时,检查获取版本号是不是数据库当前最新版本号
- 如果是[证明没有人修改数据], 执行更新, set 数据更新 , version = version+ 1
- 如果 version 不对[证明有人已经修改了],我们现在的其他记录就是失效数据!就更新失败
2、代码
1、添加版本号更新插件
- 就是在启动类中添加一个拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 把mybatis-plus的插件集合【放到集合中就行,有乐观锁,悲观锁】
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
// 分页插件
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加一个乐观锁【版本号】的拦截插件
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 添加和防止全表更新和删除的拦截器【防止删库跑路】
mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return mybatisPlusInterceptor;
}
2、乐观所字段添加@Version注解
- 这里实体类要加注解
- 数据库的表也要有version字段
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 仅支持
updateById(id)与update(entity, wrapper)方法
ALTER TABLE USER ADD VERSION INT DEFAULT 1 ; # int 类型 乐观锁字段
@Version
private Integer version;
3、测试代码
//演示乐观锁生效场景
@Test
public void testQuick7(){
//步骤1: 先查询,在更新 获取version数据
//同时查询两条,但是version唯一,最后更新的失败
User user = userMapper.selectById(5);
User user1 = userMapper.selectById(5);
user.setAge(20);
user1.setAge(30);
userMapper.updateById(user);
//乐观锁生效,失败!
userMapper.updateById(user1);
}
3、防止全表更新和删除
1、需求
1、主要是针对安歇要删库跑路的人
2、针对 update 和 delete 语句
3、作用: 阻止恶意的全表更新删除
2、实现
1、步骤
1、也是在启动类中添加一个拦截器
2、然后正常编写代码,如果删除或修改全表就会报错
2、实例
1、在启动器添加全表更新和删除拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
2、测试全部更新或者删除
@Test
public void testQuick8(){
User user = new User();
user.setName("custom_name");
user.setEmail("xxx@mail.com");
//Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
//全局更新,报错
userService.saveOrUpdate(user,null);
}