Mybatis-Plus15: 悲观锁 & 乐观锁

MyBatis-Plus 对乐观锁 提供了原生支持,但对悲观锁没有专门封装,需要借助底层 SQL 或 MyBatis 原生方式实现。


一、乐观锁(Optimistic Lock)

MyBatis-Plus 内置了乐观锁插件,核心思路是:更新时检查版本号,版本号匹配才更新,同时版本号+1

1. 实体类加 @Version 注解

java 复制代码
@Data
public class Product {
    private Long id;
    private String name;
    private Integer price;
    
    @Version  // 乐观锁版本号字段
    private Integer version;
}

2. 配置乐观锁拦截器

java 复制代码
@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加乐观锁拦截器
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

3. 使用方式

java 复制代码
// 1. 先查询出实体(version 会被查出来)
Product product = productService.getById(1L);

// 2. 修改数据
product.setPrice(product.getPrice() + 100);

// 3. 执行更新(MP 会自动在 WHERE 条件中加上 version = ?)
// 生成的 SQL 类似:
// UPDATE product SET price=?, version=version+1 WHERE id=? AND version=?
boolean success = productService.updateById(product);

4. 注意事项

注意点 说明
version 字段类型 支持 intIntegerlongLongDateTimestamp
必须查后更新 updateById(entity) 才会生效,直接 new 一个对象更新不会触发乐观锁
自增逻辑 框架自动 version + 1,无需手动设置
更新失败 如果返回 false,说明数据已被他人修改,需要业务上重试或抛异常

5. 批量更新也支持

java 复制代码
// 批量更新时,每个实体的 version 都会参与 WHERE 条件
productService.updateBatchById(productList);

二、悲观锁(Pessimistic Lock)

MyBatis-Plus 没有提供专门的悲观锁插件 ,因为悲观锁是数据库层面的机制,需要通过 SQL 的 FOR UPDATE 实现。

方式一:手写 SQL(推荐)

在 Mapper 中用 @Select 手写带 FOR UPDATE 的 SQL:

java 复制代码
public interface ProductMapper extends BaseMapper<Product> {
    
    @Select("SELECT * FROM product WHERE id = #{id} FOR UPDATE")
    Product selectByIdForUpdate(Long id);
}

Service 层调用:

java 复制代码
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> 
    implements ProductService {
    
    @Transactional  // 必须在事务中,FOR UPDATE 才有效
    public void deductStock(Long id) {
        // 1. 加锁查询
        Product product = baseMapper.selectByIdForUpdate(id);
        
        // 2. 执行业务逻辑
        if (product.getStock() > 0) {
            product.setStock(product.getStock() - 1);
            updateById(product);
        }
    }
}

方式二:用 Wrapper 拼接(不推荐,容易出问题)

java 复制代码
// 不推荐,因为 FOR UPDATE 是写在 SQL 末尾的,QueryWrapper 不好控制位置
// 而且 MP 的 selectOne 等方法不支持直接加 FOR UPDATE

方式三:XML 方式

XML 复制代码
<!-- ProductMapper.xml -->
<select id="selectByIdForUpdate" resultType="com.example.entity.Product">
    SELECT * FROM product WHERE id = #{id} FOR UPDATE
</select>

三、对比与选型

特性 乐观锁 悲观锁
MP 支持 ✅ 原生插件支持 ❌ 需手写 SQL
实现机制 版本号控制 SELECT ... FOR UPDATE
性能 高(无锁等待) 低(有锁竞争、阻塞)
适用场景 读多写少、冲突概率低 写多读少、冲突概率高、强一致性
失败处理 更新失败需重试 排队等待,不会失败
事务要求 非必须 必须在事务中

四、最佳实践建议

  1. 优先用乐观锁:绝大多数互联网场景读多写少,乐观锁性能更好,MP 支持也完善。

  2. 悲观锁用于强一致性场景:如库存扣减、金融转账等高并发写场景。

  3. 乐观锁失败重试

    java 复制代码
    // 简单的重试机制
    public boolean updateWithRetry(Product product, int maxRetries) {
        for (int i = 0; i < maxRetries; i++) {
            if (productService.updateById(product)) {
                return true;
            }
            // 重新查询最新数据
            product = productService.getById(product.getId());
        }
        throw new RuntimeException("更新失败,请重试");
    }
相关推荐
天丁o25 天前
Spring Boot + uni-app 智慧考勤闭环 Demo:打卡记录、异常状态和日统计如何复用到企业系统
spring boot·uni-app·mybatis plus·企业管理系统·考勤系统
小坏讲微服务2 个月前
SpringBoot4.0整合Spring Security+MyBatis Plus完整权限框架实现
java·spring·mybatis·spring security·mybatis plus·springboot4.0
玄〤5 个月前
个人博客网站搭建day5--MyBatis-Plus核心配置与自动填充机制详解(漫画解析)
java·后端·spring·mybatis·springboot·mybatis plus
好好研究6 个月前
MyBatis - Plus(二)常见注解 + 常见配置
数据库·spring boot·mybatis·mybatis plus
wgslucky6 个月前
SpringBoot3.x + MybatisPlus + Druid 多数据源配置及开启Druid监控与统计功能
多数据源·mybatis plus·druid监控·druid数据库
九皇叔叔6 个月前
【07】SpringBoot3 MybatisPlus 删除(Mapper)
java·开发语言·mybatis·mybatis plus
九皇叔叔6 个月前
【03】SpringBoot3 MybatisPlus BaseMapper 源码分析
java·开发语言·mybatis·mybatis plus
九皇叔叔6 个月前
【04】SpringBoot3 MybatisPlus 查询(Mapper)
java·mybatis·mybatis plus
九皇叔叔6 个月前
【01】SpringBoot3 MybatisPlus 工程创建
java·mybatis·springboot3·mybatis plus