java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis-Plus乐观锁失效

MyBatis-Plus乐观锁失效的常见原因及解决方案

作者:李少兄

在高并发场景下,乐观锁是保证数据一致性的核心工具,MyBatis-Plus 通过@Version注解和OptimisticLockerInnerInterceptor插件,为开发者提供了优雅的乐观锁实现,然而,开发者在实际使用中常遇到乐观锁“不生效”的问题,所以本文给大家介绍了乐观锁失效的常见原因及解决方案

前言

在高并发场景下,乐观锁是保证数据一致性的核心工具。MyBatis-Plus 作为 Java ORM 框架的佼佼者,通过 @Version 注解和 OptimisticLockerInnerInterceptor 插件,为开发者提供了优雅的乐观锁实现。然而,开发者在实际使用中常遇到乐观锁“不生效”的问题。

一、乐观锁的核心原理

1. 什么是乐观锁?

乐观锁(Optimistic Locking)是一种假设“冲突很少发生”(乐观锁假设大多数操作不会发生冲突,仅在更新时检查数据版本。若版本匹配,则更新成功;否则,抛出异常)的并发控制策略。其核心思想是:

2. MyBatis-Plus 的实现机制

MyBatis-Plus 通过以下三步实现乐观锁:

二、乐观锁失效的常见原因

1. 插件未正确配置

问题:未注册 OptimisticLockerInnerInterceptor 插件,导致 MyBatis-Plus 无法自动处理版本号逻辑。

示例代码

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 必须添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

2. 实体类字段未标注 @Version

问题:实体类中未使用 @Version 注解,MyBatis-Plus 无法识别版本号字段。

示例代码

import com.baomidou.mybatisplus.annotation.Version;

public class User {
    // 正确标注版本号字段
    @Version
    private Integer version;

    // 其他字段...
}

3. 数据库字段未初始化

问题:数据库表的 version 字段为 NULL 或未设置默认值,导致首次更新时无法比较版本号。

解决方案

-- 确保 version 字段有初始值
ALTER TABLE user ADD COLUMN version INT NOT NULL DEFAULT 1;

4. 更新操作未基于查询后的数据

问题:直接构造新对象更新,未读取数据库中的版本号,导致乐观锁失效。

示例代码

// 错误方式:直接构造新对象
User user = new User();
user.setId(1L);
user.setName("New Name");
userMapper.updateById(user); // version 未传递,乐观锁失效

// 正确方式:先查询数据
User user = userMapper.selectById(1L); // 查询当前数据(包含 version)
user.setName("New Name");
userMapper.updateById(user); // 自动处理 version 递增

5. 自定义 SQL 未包含版本号条件

问题:使用自定义 SQL 更新时,未显式添加 version 条件,导致乐观锁插件失效。

解决方案

<!-- 正确方式:显式添加 version 条件 -->
<update id="updateUser">
    UPDATE user
    SET name = #{name}, version = version + 1
    WHERE id = #{id} AND version = #{version}
</update>

6. 事务未正确开启

问题:乐观锁依赖事务的原子性,若事务未开启,可能导致版本号更新失败。

示例代码

@Transactional
public void updateData(Long id, String newName) {
    User user = userMapper.selectById(id); // 查询数据
    user.setName(newName);
    userMapper.updateById(user); // 事务提交后版本号递增
}

7. 并发测试未触发冲突

问题:未模拟并发场景,无法观察到乐观锁的校验效果。

测试代码

@Test
public void testOptimisticLock() {
    User user1 = userMapper.selectById(1L); // version=1
    User user2 = userMapper.selectById(1L); // version=1

    user1.setName("Thread 1");
    userMapper.updateById(user1); // version 变为 2

    user2.setName("Thread 2");
    try {
        userMapper.updateById(user2); // 此处应抛出 OptimisticLockException
    } catch (OptimisticLockException e) {
        System.out.println("乐观锁生效,更新失败");
    }
}

8. 版本字段类型不匹配

问题@Version 注解支持的类型为 Integer/Long/Date/Timestamp/LocalDateTime,若使用其他类型(如 String),会导致乐观锁失效。

示例代码

// 正确类型
@Version
private Integer version; // 或 Long/LocalDateTime

// 错误类型
@Version
private String version; // 类型不匹配,乐观锁失效

9. MyBatis-Plus 版本过低

问题:旧版本可能存在 Bug 或功能限制。
解决方案:升级至最新版本(建议 3.5.7+)。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.7</version>
</dependency>

三、完整解决方案示例

1. 数据库表设计

CREATE TABLE user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100),
    version INT NOT NULL DEFAULT 1 -- 初始化版本号
);

2. 实体类配置

import com.baomidou.mybatisplus.annotation.*;

@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;

    private String name;

    @Version
    private Integer version; // 版本号字段

    // Getter & Setter
}

3. 配置插件

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

4. 业务逻辑代码

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void updateUser(Long id, String newName) {
        User user = userMapper.selectById(id); // 查询当前数据
        user.setName(newName);
        int result = userMapper.updateById(user); // 自动处理 version
        if (result == 0) {
            throw new OptimisticLockException("乐观锁更新失败");
        }
    }
}

四、进阶技巧与注意事项

1. 自定义版本号字段名

若数据库字段名与实体类字段名不一致,可通过 @TableId 或 @TableField 指定映射:

@TableField("ver")
@Version
private Integer version;

2. 多条件更新的版本号校验

在复杂更新场景中,需手动处理版本号逻辑:

UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 1L).eq("version", user.getVersion());
user.setVersion(user.getVersion() + 1);
userMapper.update(user, wrapper);

3. 分布式环境下的版本号一致性

在分布式系统中,确保所有节点共享同一数据库,避免版本号冲突。若需跨节点同步,可结合 Redis 或数据库中间件实现。

4. 性能优化建议

五、总结

MyBatis-Plus 的乐观锁机制本质是“通过版本号比对保证数据一致性”,但其生效依赖多个环节的正确配置与实现。以下是关键要点回顾:

关键点说明
插件配置必须注册 OptimisticLockerInnerInterceptor
实体类注解使用 @Version 标注版本号字段
数据库初始化version 字段必须有非空初始值
更新逻辑基于查询后的数据更新,避免直接构造新对象
自定义 SQL显式添加 version 条件
事务管理使用 @Transactional 确保事务一致性
并发测试模拟多线程场景验证乐观锁效果
字段类型与版本兼容性确保字段类型为 Integer/Long/LocalDateTime,升级 MyBatis-Plus 至最新版

官方文档 MyBatis-Plus 乐观锁指南

以上就是MyBatis-Plus乐观锁失效的常见原因及解决方案的详细内容,更多关于MyBatis-Plus乐观锁失效的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文