悲观锁、乐观锁、mybatis-plus实现乐观锁

悲观锁、乐观锁、mybatis-plus实现乐观锁

转载自:www.javaman.cn

1、悲观锁、乐观锁

乐观锁和悲观锁是两种用于处理并发操作的数据锁定策略。它们在处理多个事务尝试同时访问和修改同一数据时的方法有所不同。

  1. 悲观锁 (Pessimistic Locking)
    • 概念:悲观锁是一种基于悲观态度的数据并发控制机制。它总是假设最坏的情况,即认为其他事务会尝试修改数据,因此在读取数据时就会加锁,以确保在此期间其他事务不能修改数据。
    • 工作原理:当一个事务想要读取或修改数据时,它会先请求锁。如果锁已经被其他事务持有,则当前事务会被阻塞,直到锁被释放。这确保了每次只有一个事务可以修改数据。
    • 使用场景:悲观锁适用于写操作较多的场景,或者当数据冲突可能性较高时。它可以在数据库层面实现,如行锁、表锁等。
    • 优点:能够有效防止数据冲突,确保数据的一致性。
    • 缺点:如果锁的粒度过大或持有时间过长,可能导致并发性能下降和资源浪费。
  2. 乐观锁 (Optimistic Locking)
    • 概念:乐观锁是基于乐观态度的数据并发控制机制。它总是假设最好的情况,即在读取数据和提交更新之间,其他事务不会修改数据。因此,它不会在读取数据时加锁,而是在更新数据时检查是否有其他事务已经修改了数据。
    • 工作原理:通常通过版本号、时间戳等机制实现。当事务想要更新数据时,它会检查数据的版本号是否与自己最初读取的版本号相匹配。如果匹配,说明没有其他事务修改过数据,可以进行更新;否则,更新会被拒绝。
    • 使用场景:乐观锁适用于读操作较多、写操作较少的场景,或者当数据冲突可能性较低时。它通常在应用层面实现。
    • 优点:在高并发场景下可以提高性能,因为它减少了不必要的锁等待时间。
    • 缺点:如果有很多写操作或者数据冲突频繁发生,可能导致大量的重试和回滚,从而降低性能。

实战中使用

  • 悲观锁 :在关系型数据库中,如MySQL,可以使用SELECT ... FOR UPDATE语句对选定的行或表加锁。在Java中,可以使用synchronized关键字或ReentrantLock等来实现悲观锁。
  • 乐观锁 :可以在应用层面实现,例如在更新数据时检查数据的版本号是否发生变化。在MyBatis-Plus中,可以通过OptimisticLockerInnerInterceptor插件来简化乐观锁的使用。

2、mybatis-plus实现乐观锁

MyBatis-plus本身并不直接实现悲观锁或乐观锁,而是提供了与数据库的交互机制,使得开发者能够在应用中实现这两种锁策略。但是,为什么我们经常会看到 MyBatis 与乐观锁的结合,而与悲观锁的结合较少呢?这主要是因为两者在使用场景和适用性上的差异。

  1. 悲观锁
    • 悲观锁主要在数据库层面实现,例如通过 SQL 语句。
    • 当使用悲观锁时,事务在读取数据时就会加锁,确保在此期间其他事务不能修改数据。这涉及到数据库的并发控制和锁定机制,与 MyBatis 的 ORM 功能关系不大。
    • 由于悲观锁的实现更多地依赖于数据库本身的功能,因此 MyBatis 在这方面并没有太多可以增加的价值。
  2. 乐观锁
    • 乐观锁通常在应用层面实现,需要开发者检查数据的版本号、时间戳等来判断数据是否在读取后被其他事务修改。
    • MyBatis-Plus 提供的 OptimisticLockerInnerInterceptor 插件可以简化这一过程,帮助开发者更容易地在应用中实现乐观锁。
    • 由于乐观锁的实现更多地依赖于应用逻辑,MyBatis 在这方面的插件和工具可以为开发者带来便利。

总结:MyBatis 本身并不防止悲观锁或乐观锁,而是提供了与数据库的交互机制。悲观锁主要依赖于数据库的功能来实现,而乐观锁则可以在应用层面得到 MyBatis-Plus 等工具的帮助。

乐观锁的基础使用

MyBatis-Plus 提供了一个内置的乐观锁插件 OptimisticLockerInnerInterceptor 来帮助简化乐观锁的实现。以下是使用 MyBatis-Plus 实现乐观锁的基本步骤:

​ 1.添加乐观锁插件

在你的 MyBatis-Plus 配置中添加乐观锁插件。

java 复制代码
@Configuration
@MapperScan({"com.ds.blog.system.mapper","com.ds.blog.admin.mapper"})
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //乐观锁插件
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }

​ 2.标记乐观锁字段

在你的实体类中,使用 @Version 注解来标记作为乐观锁字段的属性,通常是版本号或时间戳。

java 复制代码
public class Entity {  
    @Version  
    private Integer version;  
    // 其他字段和方法  
}
  1. 更新操作
    当你尝试更新数据时,MyBatis-Plus 会自动检查乐观锁字段。如果数据的版本号与你最初读取的版本号不匹配,更新会被拒绝,并抛出 OptimisticLockException 异常。
  2. 处理乐观锁异常
    在你的业务代码中,需要捕获并处理 OptimisticLockException 异常。通常的做法是重试更新操作或通知用户数据已被其他事务修改。
  3. 自定义乐观锁逻辑
    如果需要更复杂的乐观锁逻辑,你可以实现自己的乐观锁拦截器,并覆盖默认的行为。

注意:乐观锁策略的成功与否还取决于你的并发控制策略和业务场景。

自定义乐观锁拦截器

具体步骤如下:

​ 1.创建自定义拦截器类

首先,你需要创建一个类来实现 MyBatis-Plus 的 InnerInterceptor 接口。这个接口定义了一些方法,允许你在 SQL 执行的不同阶段插入自定义逻辑。

java 复制代码
import com.baomidou.mybatisplus.core.parser.SqlInfo;  
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;  
import org.apache.ibatis.executor.Executor;  
import org.apache.ibatis.mapping.MappedStatement;  
import org.apache.ibatis.plugin.*;  
  
import java.util.Properties;  
  
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})  
public class CustomOptimisticLockInterceptor implements InnerInterceptor {  
    // 你的自定义逻辑将在这里实现  
}

​ 2.实现拦截方法

在你的自定义拦截器类中,你需要实现 processBeforeprocessAfter 方法。这些方法在 SQL 执行之前和之后被调用,允许你插入自定义逻辑。在这个例子中,我们关注 processBefore 方法,因为它允许我们在更新操作之前检查乐观锁。

java 复制代码
@Override  
public void processBefore(Executor executor, MappedStatement ms, Object parameter, String sql, SqlInfo sqlInfo) {  
    // 获取当前事务尝试更新的实体对象  
    Entity entity = (Entity) parameter;  
    // 获取原始版本号(通常从数据库中读取)  
    int originalVersion = entity.getVersion();  
    // 检查版本号是否已经被修改(意味着有其他事务已经更新了这条记录)  
    if (originalVersion != entity.getVersion()) {  
        throw new OptimisticLockException("数据已被其他事务修改");  
    }  
    // 如果需要检查其他字段或执行其他逻辑,可以在这里添加代码  
}

​ 3.注册自定义拦截器

在你的 MyBatis-Plus 配置中,你需要注册你的自定义拦截器,以便它能够被正确地应用到你的 SQL 执行过程中。

java 复制代码
@Configuration
@MapperScan({"com.ds.blog.system.mapper","com.ds.blog.admin.mapper"})
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();  
		mybatisPlusInterceptor.addInnerInterceptor(new CustomOptimisticLockInterceptor());
        return mybatisPlusInterceptor;
    }