源本科技 | 码上会

乐观锁插件

2026/04/04
3
0

引言

乐观锁是一种轻量级并发控制机制,核心思想是假设并发冲突极少发生,在数据提交更新时才校验是否被其他事务修改。 它区别于悲观锁(直接锁定数据),无需阻塞线程,适用于读多写少的高并发业务场景,能有效避免并发更新导致的数据覆盖问题。

实现原理

MyBatis-Plus 基于版本号机制实现乐观锁,执行流程如下:

  1. 查询数据时,同步获取记录对应的 version 版本号;

  2. 更新数据时,将获取到的旧版本号作为更新条件;

  3. 执行更新 SQL,自动设置 version = 旧版本号 + 1

  4. 若版本号匹配,更新成功且版本号自增;若版本号不匹配,说明数据已被修改,更新失败。

最终执行的 SQL 示例:

UPDATE user SET name = ?, version = version + 1 WHERE id = ? AND version = 旧版本号

插件作用

OptimisticLockerInnerInterceptor 是 MyBatis-Plus 提供的乐观锁拦截器,无需手动编写版本号校验逻辑,自动拦截更新操作并拼接版本号条件,实现无侵入式乐观锁控制。

环境准备

MyBatis-Plus 3.4.0 及以上版本支持该插件,无需额外引入依赖,核心包已内置。

插件配置

插件需注册到 MybatisPlusInterceptor 插件链中,多插件配置时需注意执行顺序。

Spring Boot 配置

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("填写你的 Mapper 接口扫描包路径")
public class MybatisPlusConfig {

    /**
     * 注册 MyBatis-Plus 插件
     * 多插件规则:乐观锁插件 → 分页插件(分页必须最后)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加乐观锁拦截器
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 若配置分页插件,需放在乐观锁之后
        // interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

实体类配置

@Version 注解

在实体类的版本号字段上添加 @Version 注解,标记该字段为乐观锁版本字段:

import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;

@Data
public class User {
    // 主键
    private Long id;
    // 业务字段
    private String name;
    // 乐观锁版本字段
    @Version
    private Integer version;
}

支持的数据类型

插件支持以下 7 种数据类型,整数类型会自动 +1,时间类型会自动更新为当前时间:

  1. 整数类型:intIntegerlongLong

  2. 时间类型:DateTimestampLocalDateTime


使用规则

  1. 版本自动回写 更新成功后,新的版本号会自动赋值到传入的实体对象中,无需手动赋值。

  2. 支持的内置方法

    • updateById(entity)

    • update(entity, wrapper)

    • saveOrUpdate(entity)

    • insertOrUpdate(entity)(MP 3.5.7 及以上版本支持)

  3. 自定义方法支持 自定义 Mapper 方法满足「参数为实体对象」时,插件会自动填充乐观锁逻辑;纯 SQL 更新需手动编写版本号条件。

代码示例

添加版本字段

ALTER TABLE user ADD COLUMN version INT DEFAULT 1 NOT NULL COMMENT '乐观锁版本号';

业务层调用

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    /**
     * 乐观锁更新示例
     */
    public void updateUser() {
        // 1. 查询数据(获取旧版本号)
        User user = userMapper.selectById(1L);
        
        // 2. 修改业务数据
        user.setName("乐观锁测试");

        // 3. 执行更新(插件自动拼接版本号条件)
        int rows = userMapper.updateById(user);
        if (rows == 0) {
            System.out.println("更新失败,数据已被其他事务修改!");
        } else {
            System.out.println("更新成功,新版本号:" + user.getVersion());
        }
    }
}

注意事项

  1. 多插件执行顺序 若同时配置分页、防全表更新等插件,乐观锁必须放在分页插件之前,避免拦截逻辑异常。

  2. Wrapper 不可复用 使用 update(entity, wrapper) 时,Wrapper 对象不能复用,否则会导致版本号条件重复拼接。

  3. 不支持的操作 插件仅拦截更新操作,删除、查询、无实体对象的纯 Wrapper 更新均不生效。

  4. 版本号初始值 新增数据时,建议在数据库设置版本号默认值为 1,或通过实体类默认值初始化。

  5. 并发冲突处理 更新失败后,可根据业务需求实现重试机制,保证数据最终一致性。

  6. 分布式场景 乐观锁仅适用于单服务实例,分布式系统需结合 Redis、ZooKeeper 实现分布式锁。

常见问题

  1. 乐观锁不生效

    • 未注册 OptimisticLockerInnerInterceptor 插件;

    • 实体类未添加 @Version 注解;

    • 更新方法无实体对象参数。

  2. 版本号不递增 数据类型不支持,仅支持指定的 7 种类型。

  3. 更新失败无提示 需判断更新返回的影响行数,行数为 0 则代表并发冲突。

总结

  1. 乐观锁是高并发场景下轻量级的并发控制方案,MyBatis-Plus 通过插件实现无侵入式使用。

  2. 核心配置两步:注册插件 + 实体类添加 @Version 注解。

  3. 仅支持带实体对象的更新方法,多插件需严格遵循执行顺序。

  4. 适用于读多写少的业务,分布式场景需搭配分布式锁使用。