乐观锁是一种轻量级并发控制机制,核心思想是假设并发冲突极少发生,在数据提交更新时才校验是否被其他事务修改。 它区别于悲观锁(直接锁定数据),无需阻塞线程,适用于读多写少的高并发业务场景,能有效避免并发更新导致的数据覆盖问题。
MyBatis-Plus 基于版本号机制实现乐观锁,执行流程如下:
查询数据时,同步获取记录对应的 version 版本号;
更新数据时,将获取到的旧版本号作为更新条件;
执行更新 SQL,自动设置 version = 旧版本号 + 1;
若版本号匹配,更新成功且版本号自增;若版本号不匹配,说明数据已被修改,更新失败。
最终执行的 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 注解,标记该字段为乐观锁版本字段:
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,时间类型会自动更新为当前时间:
整数类型:int、Integer、long、Long
时间类型:Date、Timestamp、LocalDateTime
版本自动回写 更新成功后,新的版本号会自动赋值到传入的实体对象中,无需手动赋值。
支持的内置方法
updateById(entity)
update(entity, wrapper)
saveOrUpdate(entity)
insertOrUpdate(entity)(MP 3.5.7 及以上版本支持)
自定义方法支持 自定义 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());
}
}
}多插件执行顺序 若同时配置分页、防全表更新等插件,乐观锁必须放在分页插件之前,避免拦截逻辑异常。
Wrapper 不可复用 使用 update(entity, wrapper) 时,Wrapper 对象不能复用,否则会导致版本号条件重复拼接。
不支持的操作 插件仅拦截更新操作,删除、查询、无实体对象的纯 Wrapper 更新均不生效。
版本号初始值 新增数据时,建议在数据库设置版本号默认值为 1,或通过实体类默认值初始化。
并发冲突处理 更新失败后,可根据业务需求实现重试机制,保证数据最终一致性。
分布式场景 乐观锁仅适用于单服务实例,分布式系统需结合 Redis、ZooKeeper 实现分布式锁。
乐观锁不生效
未注册 OptimisticLockerInnerInterceptor 插件;
实体类未添加 @Version 注解;
更新方法无实体对象参数。
版本号不递增 数据类型不支持,仅支持指定的 7 种类型。
更新失败无提示 需判断更新返回的影响行数,行数为 0 则代表并发冲突。
乐观锁是高并发场景下轻量级的并发控制方案,MyBatis-Plus 通过插件实现无侵入式使用。
核心配置两步:注册插件 + 实体类添加 @Version 注解。
仅支持带实体对象的更新方法,多插件需严格遵循执行顺序。
适用于读多写少的业务,分布式场景需搭配分布式锁使用。