目录
一、乐观锁、悲观锁
1、什么是乐观锁和悲观锁
①乐观锁(Optimistic Locking):
②悲观锁(Pessimistic Locking):
③实现方式
2、乐观锁和悲观锁的区别
①乐观锁(Optimistic Locking)
②悲观锁(Pessimistic Locking)
③总结
3、案例
①如何实现乐观锁?
②如何使用MyBatis-Plus实现乐观锁?
二、逻辑删除
1、什么是逻辑删除
2、为什么使用逻辑删除
3、案例
①官方说明
②配置方式
③演示
三、分页和查询构造器
1、查询构造器
2、分页(PaginationInnerInterceptor)
一、乐观锁、悲观锁
1、什么是乐观锁和悲观锁
①乐观锁(Optimistic Locking):
- 乐观锁假设多个事务在大多数情况下不会相互冲突,因此在数据读取时不会立即加锁。
- 当数据需要更新时,会检查在此期间是否有其他事务对该数据进行过修改。如果有,则当前事务会失败并回滚。
- 乐观锁通常通过版本号或时间戳来实现,其中读取数据时获取版本号或时间戳,更新数据时检查版本号或时间戳是否发生变化。
②悲观锁(Pessimistic Locking):
- 悲观锁则相反,它假设多个事务在大多数情况下会相互冲突,因此在数据读取时就直接加锁。
- 这意味着其他事务在加锁释放之前无法访问该数据。
- 悲观锁可以通过数据库的行级锁、表级锁等方式实现。
③实现方式
- 乐观锁: MyBatis Plus 支持使用
Version
注解实现乐观锁。在实体类中,可以添加一个版本字段并使用Version
注解来标记为乐观锁字段。在更新操作时,MyBatis Plus 会自动检查版本号是否发生变化,以决定是否允许更新。 - 悲观锁: MyBatis Plus 提供了
Lock
注解支持悲观锁。当需要在更新操作时对记录加锁时,可以使用Lock
注解标记对应的 SQL 语句。这会在执行该 SQL 语句时自动加锁,以确保数据的一致性。
2、乐观锁和悲观锁的区别
①乐观锁(Optimistic Locking)
- 在操作数据时持乐观态度,认为数据在并发操作期间不太可能被其他事务修改,因此不会立即加锁。
- 在更新数据时,会检查在此期间是否有人修改了数据。如果检测到数据已被修改,则放弃更新操作;否则,执行更新。
- 乐观锁适用于读操作频繁、写操作较少的场景,因为不加锁的特性在性能方面可能优于悲观锁。
②悲观锁(Pessimistic Locking)
- 在操作数据时持悲观态度,认为数据在并发操作期间很可能被其他事务修改。
- 因此,在读取数据时就直接对数据进行加锁,防止其他事务同时修改,直到当前事务完成操作并释放锁。
- 悲观锁适用于写操作频繁的场景,可以避免因数据被其他事务修改而导致的冲突。
③总结
-
加锁时间不同:乐观锁在读取数据时不会对其加锁,而是在写入时进行比较和加锁操作;悲观锁在读取数据时就会对其加锁。
-
冲突处理方式不同:乐观锁会在写入时进行比较和冲突检测,如果版本号不一致则操作失败,需要重新读取数据;悲观锁则会阻塞其他进程对该数据的访问,直到当前进程完成操作并解锁。
-
适用场景不同:乐观锁适用于并发量比较小、数据量比较大、操作更多为读取的场景;悲观锁适用于并发量比较大、数据量比较小、操作更多为写入的场景。
3、案例
①如何实现乐观锁?
@Version注解标记乐观锁,通过 version 字段来保证数据的安全性,当修改数据的时候,会以 version 作为条件,当条件成立的时候才会修改成功。
1)取出记录时,获取当前 version
2)更新时,带上这个 version
3)执行更新时,`update tableName set version = oldVersion + 1 where version = oldVersion`
4)如果 version 不对,就更新失败
②如何使用MyBatis-Plus实现乐观锁?
- 给数据库表添加 version 字段,并设置默认值为1
- 实体类增加 version 属性,并添加 @Version 注解
/** * 乐观锁 */ @Version private Integer version;
- 配置乐观锁插件
@Configuration public class MybatisPlusConfig { /** * 添加分页插件 * 乐观锁插件 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }
- 测试
从控制台的日志信息发现:修改数据时 version 作为条件判断,并且 version 自动完成自增操作,即:@Test void contextLoads() { //先查询,再修改 Book book = bookMapper.selectById("1685091066406"); book.setBookname("Java编程思想"); book.setPrice(100f); bookMapper.updateById(book); }
version = version+1
。
测试多线程下乐观锁失败:@Test public void demo2(){ //线程1: Book book1 = bookMapper.selectById("1685091066406"); book1.setBookname(".Net之入门"); book1.setPrice(110f); //线程2:(在线程1的修改操作未来得及执行时介入) Book book2 = bookMapper.selectById("1685091066406"); book2.setBookname("Python之入门"); book2.setPrice(200f); bookMapper.updateById(book2); //如果没有乐观锁就会覆盖插队线程的值! bookMapper.updateById(book1);//更新失败 }
总结:就是一次
二、逻辑删除
1、什么是逻辑删除
逻辑删除是指文件没有被真正的删除,只不过是文件名的第一个字节被改成操作系统无法识别的字符。这种删除操作通常是可逆的,也就是说,用适当的工具或软件可以把删除的文件恢复出来。逻辑删除和物理删除是两种不同的文件删除方式。在计算机中,资料数据等都以文件形式存储。当文件需要被删除时,可以根据实际需求选择逻辑删除或物理删除。
2、为什么使用逻辑删除
原因、作用:
- 数据恢复:逻辑删除只是修改了文件名或其他元数据,并没有真正删除数据。因此,被删除的数据仍然存在于存储介质中,可以被恢复。这种设计方便数据恢复,保护了数据本身的价值。
- 保护数据完整性:逻辑删除操作不会对数据本身造成损害,从而保护了数据的完整性。
- 操作简单:逻辑删除操作相对简单,只需修改元数据即可,而无需清空存储介质或进行其他复杂操作。
- 灵活性高:逻辑删除允许在将来某个时间点恢复被删除的数据,因此具有很高的灵活性。
- 减少存储空间占用:物理删除需要清空存储介质,可能会浪费大量的存储空间。相比之下,逻辑删除不会占用额外存储空间。
- 满足法规和政策要求:在某些行业或应用场景中,法规和政策可能要求保留被删除的数据,以便进行审计或追溯。逻辑删除可以满足这些要求,而不会造成数据丢失。
3、案例
①官方说明
只对自动注入的 sql 起效:
- 插入: 不作限制
- 查找: 追加 where 条件过滤掉已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
- 更新: 追加 where 条件防止更新到已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动追加该字段
- 删除: 转变为 更新
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
字段类型支持说明:
- 支持所有数据类型(推荐使用
Integer
,Boolean
,LocalDateTime
) - 如果数据库字段使用
datetime
,逻辑未删除值和已删除值支持配置为字符串null
,另一个值支持配置为函数来获取值如now()
附录:
- 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
- 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。
②配置方式
全局配置
例: application.yml
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
在对应的实体类中添加逻辑删除字段,如下:
//逻辑删除
@TableField("statu")
private Integer statu;
局部配置
请在实体类对应的逻辑删除属性上加入@TableLogic
注解。其中@TableLogic
注解属性介绍如下:
属性名 | 类型 | 说明 |
---|---|---|
value | String | 未逻辑删除的值 |
delval | String | 已逻辑删除的值 |
在实体类上配置逻辑删除字段,如下:
/**
* 逻辑删除,1=删除,0=正常
*/
@TableLogic(value = "0",delval = "1")
@TableField("statu")
private Integer statu;
③演示
创建junit
测试,使用deleteById
方法进行测试。
@Test
void contextLoads() {
//先使用deleteById删除对应的数据
bookMapper.deleteById("493783504224325");
//使用查询方法查询数据
//List<Book> books = bookMapper.selectList(null);
//books.forEach(System.out::println);
}
观察idea
控制台输出结果,会发现执行deleteById
方法后,不再显示delete语句,而是update语句。则表示逻辑删除成功,可查看MySQL
数据表中的结果。
然后,执行selectList(null)
方法,可发现查询语句的where
条件后加入逻辑删除字段deleted=0
的判断处理,表示只查询出未逻辑删除的数据。
三、分页和查询构造器
1、查询构造器
QueryWrapper是Mybatis-Plus提供的一个条件构造器,用于快速构建SQL查询语句的条件部分。通过使用QueryWrapper,我们可以方便地进行单表数据的查询、修改、删除等操作。
QueryWrapper的语法类似于Mybatis的XML文件中的where标签,其最终会被转换为SQL语句的条件部分。我们可以通过链式调用的方式,不断添加查询条件,从而构建出复杂的查询条件。
2、分页(PaginationInnerInterceptor)
支持的数据库
-
mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb,informix,TDengine,redshift
-
达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库,星瑞格数据库
配置分页插件
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//注册分页插件
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return mybatisPlusInterceptor;
}
}
实现分页
int page=1;
int row=10;
//条件构造器
QueryWrapper<Book> wrapper=new QueryWrapper<>();
//设置条件
//TODO
//设置分页
Page<Book> result = bookMapper.selectPage(new Page<Book>()
.setCurrent(page)
.setSize(row)
, wrapper);
List<Book> records = result.getRecords();
System.out.println("总记录数:"+result.getTotal());
records.forEach(System.out::println);