MyBatis-Plus 提供的 乐观锁插件 是一种处理并发修改问题的有效方式,适用于多种高并发场景。通过 @Version 注解和 OptimisticLockerInnerInterceptor 插件,可以轻松实现对数据的并发控制。


一、核心概念

1.1 什么是乐观锁?

乐观锁是一种并发控制机制,它假设数据在大多数情况下不会发生冲突,因此不会锁定资源。只有在提交更新时才会检查数据是否被其他事务修改过。如果检测到冲突,则拒绝更新并提示用户重试或合并更改。

1.2 实现原理

  • 版本号字段(version:每个记录都有一个版本号,每次更新该记录时,版本号都会递增。
  • 更新条件:更新时会在 SQL 的 WHERE 子句中加入版本号条件,如 UPDATE ... SET ... WHERE id = ? AND version = ?
  • 更新失败:如果返回影响行数为 0,说明数据已被其他事务修改,当前更新失败。

1.3 适用场景

  • 读多写少:适用于读取频繁但写入较少的场景。
  • 高并发环境:例如电商系统的库存管理、秒杀活动等。
  • 避免脏写:确保数据的一致性,防止多个用户同时修改同一数据导致的数据不一致问题。

1.4 不适用场景

  • 写密集型应用:如果系统中有大量的并发写操作,可能会导致较高的冲突率,此时应考虑悲观锁或其他方案。
  • 实时性要求极高的系统:对于需要强一致性且不允许任何数据丢失或延迟的应用,可能不适合使用乐观锁。

二、操作步骤(非常详细)

步骤 1:准备数据库表结构

首先,在数据库中创建一个包含 version 字段的表:

CREATE TABLE `user` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` VARCHAR(50) DEFAULT NULL COMMENT '用户名',
  `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
  `version` INT DEFAULT 1 COMMENT '乐观锁版本号',
  PRIMARY KEY (`id`)
);

步骤 2:实体类定义

接下来,在对应的实体类上添加 @Version 注解以标识版本字段:

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

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    
    private String email;
    
    @Version // 标识乐观锁版本字段
    private Integer version;
}

步骤 3:配置乐观锁插件

在 Spring Boot 配置类中注册 MybatisPlusInterceptor 并添加 OptimisticLockerInnerInterceptor

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

@Configuration
public class MyBatisPlusConfig {

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

步骤 4:编写业务逻辑

在服务层编写更新逻辑,并确保传入了最新的 version 值:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public boolean updateUser(User user) {
        // 确保传入了正确的 version 值
        int result = userMapper.updateById(user);
        return result > 0; // 返回是否更新成功
    }
}

三、常见错误与解决方案

错误 原因 解决方案
更新失败但无提示 未正确处理 updateById 的返回值 检查返回值是否大于 0
版本号字段未参与 WHERE 条件 未正确配置插件或实体类缺少 @Version 注解 确认插件已正确配置且实体类已添加注解
version 字段为 null 数据库中没有设置默认值或者插入时未赋值 设置合理的默认值,确保插入时 version 字段有值

四、注意事项

  1. version 字段必须非空:无论是数据库层面还是 Java 对象层面,都需要保证 version 字段有值。
  2. 更新时需传入最新 version:前端需要将获取到的 version 值传回给后端,以便进行对比。
  3. 不支持批量更新:乐观锁机制下,updateBatchById 方法不支持自动增加版本号,需要逐条更新。
  4. 避免手动设置 version:除了初始化外,尽量不要手动修改 version 字段,让框架自动维护。

五、使用技巧

5.1 结合自动填充功能

可以利用 MyBatis-Plus 的自动填充功能来初始化 version 字段:

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "version", Integer.class, 1);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // 不需要在此处做任何事情
    }
}

5.2 异常处理与用户体验优化

当遇到版本冲突时,可以通过异常处理机制给出友好的提示信息,比如提示用户“数据已被他人修改,请刷新页面后重试”。

try {
    userService.updateUser(user);
} catch (Exception e) {
    log.error("Update failed due to optimistic lock conflict.", e);
    throw new RuntimeException("数据已被他人修改,请刷新页面后重试");
}

六、最佳实践与性能优化

6.1 最佳实践

  • 统一基类封装 version 字段:可以创建一个基础实体类,所有需要乐观锁保护的实体类继承自该基类。

    @Data
    public abstract class BaseEntity {
        @Version
        private Integer version;
    }
    
    public class User extends BaseEntity {
        // 用户实体类内容
    }
    
  • 日志记录冲突事件:在发生版本冲突时记录日志,便于后续分析问题原因。

6.2 性能优化

  • 减少不必要的查询:由于乐观锁仅在更新时才发挥作用,因此不需要额外的查询开销。
  • 适合分布式环境:相比于悲观锁,乐观锁不需要跨节点协调锁,更适合分布式系统。

通过上述介绍,您应该能够快速掌握 MyBatis-Plus 中乐观锁的基本用法及其应用场景,并能够在实际项目中灵活运用这一特性来提高系统的并发处理能力和数据一致性。