MyBatis-Plus乐观锁案例

wen java案例 2

本文目录导读:

MyBatis-Plus乐观锁案例

  1. 数据库表结构
  2. Maven依赖
  3. 配置文件 (application.yml)
  4. 实体类
  5. 配置乐观锁插件
  6. 自定义MetaObjectHandler(自动填充)
  7. Mapper接口
  8. Service层
  9. Service实现类
  10. 测试类
  11. 启动类
  12. SQL日志输出示例
  13. 关键点说明

我来为你提供一个完整的MyBatis-Plus乐观锁案例。

数据库表结构

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `email` varchar(50) DEFAULT NULL COMMENT '邮箱',
  `version` int(11) DEFAULT '0' COMMENT '版本号',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 插入测试数据
INSERT INTO `user` (`id`, `name`, `age`, `email`, `version`) VALUES
(1, '张三', 20, 'zhangsan@example.com', 0),
(2, '李四', 25, 'lisi@example.com', 0),
(3, '王五', 30, 'wangwu@example.com', 0);

Maven依赖

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- MyBatis-Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- 测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

配置文件 (application.yml)

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

实体类

package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user")
public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    /**
     * 版本号字段
     * 实现乐观锁的关键注解
     */
    @Version
    private Integer version;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

配置乐观锁插件

package com.example.demo.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.example.demo.mapper")
public class MybatisPlusConfig {
    /**
     * MyBatis-Plus插件配置
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 分页插件
        PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
        paginationInterceptor.setMaxLimit(500L);
        paginationInterceptor.setOverflow(false);
        interceptor.addInnerInterceptor(paginationInterceptor);
        return interceptor;
    }
}

自定义MetaObjectHandler(自动填充)

package com.example.demo.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        // 初始版本号为0
        this.strictInsertFill(metaObject, "version", Integer.class, 0);
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

Mapper接口

package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 继承BaseMapper即可获得基础CRUD方法
}

Service层

package com.example.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.User;
public interface UserService extends IService<User> {
    /**
     * 模拟并发更新场景
     */
    boolean transferWithOptimisticLock(Long userId, int incrementAge);
    /**
     * 使用UpdateWrapper进行乐观锁更新
     */
    boolean updateByOptimisticLock(User user);
}

Service实现类

package com.example.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    /**
     * 模拟并发更新场景
     * 两个线程同时修改同一条数据
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean transferWithOptimisticLock(Long userId, int incrementAge) {
        // 1. 查询用户(此时version=0)
        User user = this.getById(userId);
        System.out.println("读取用户信息: " + user);
        // 2. 模拟数据处理耗时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        // 3. 修改数据(update时会自动带上version条件)
        user.setAge(user.getAge() + incrementAge);
        boolean result = this.updateById(user);
        if (result) {
            System.out.println("更新成功,新版本号: " + user.getVersion());
        } else {
            System.out.println("更新失败,版本号冲突");
        }
        return result;
    }
    /**
     * 使用UpdateWrapper进行乐观锁更新
     */
    @Override
    public boolean updateByOptimisticLock(User user) {
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", user.getId())
                   .eq("version", user.getVersion());  // 手动加version条件
        // 更新字段
        updateWrapper.set("age", user.getAge())
                    .set("email", user.getEmail());
        return this.update(updateWrapper);
    }
}

测试类

package com.example.demo;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@SpringBootTest
class OptimisticLockTest {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserService userService;
    /**
     * 测试基本乐观锁更新
     */
    @Test
    void testOptimisticLock() {
        // 1. 查询用户
        User user = userMapper.selectById(1L);
        System.out.println("原始数据: " + user);
        // 2. 修改数据
        user.setAge(user.getAge() + 1);
        userMapper.updateById(user);
        // 3. 查询验证
        User updatedUser = userMapper.selectById(1L);
        System.out.println("更新后数据: " + updatedUser);
        // 断言版本号增加
        assert updatedUser.getVersion() == user.getVersion();
    }
    /**
     * 测试并发更新(模拟多线程竞争)
     */
    @Test
    void testConcurrentUpdate() throws InterruptedException {
        // 重置用户数据
        User user = userMapper.selectById(2L);
        user.setAge(25);
        user.setVersion(0);
        userMapper.updateById(user);
        // 创建并发任务
        CountDownLatch latch = new CountDownLatch(5);
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            final int threadNum = i;
            executor.execute(() -> {
                try {
                    System.out.println("线程 " + threadNum + " 开始更新");
                    boolean success = userService.transferWithOptimisticLock(2L, 1);
                    System.out.println("线程 " + threadNum + " 更新结果: " + success);
                } finally {
                    latch.countDown();
                }
            });
        }
        latch.await();
        executor.shutdown();
        // 查询最终结果
        User finalUser = userMapper.selectById(2L);
        System.out.println("最终结果: " + finalUser);
        // 最终age应该只增加1(只有第一个线程成功)
        assert finalUser.getAge() == 26;
    }
    /**
     * 测试版本号冲突
     */
    @Test
    void testVersionConflict() {
        // 模拟两个线程同时读取
        User user1 = userMapper.selectById(3L);
        User user2 = userMapper.selectById(3L);
        System.out.println("用户1读取: " + user1);
        System.out.println("用户2读取: " + user2);
        // 用户1先更新
        user1.setAge(user1.getAge() + 10);
        boolean update1 = userMapper.updateById(user1);
        System.out.println("用户1更新成功: " + update1);
        // 用户2后更新(此时version已经改变)
        user2.setAge(user2.getAge() + 20);
        boolean update2 = userMapper.updateById(user2);
        System.out.println("用户2更新成功: " + update2);
        // 用户2的更新应该失败
        assert update1 && !update2;
    }
    /**
     * 测试批量更新
     */
    @Test
    void testBatchUpdate() {
        userMapper.selectList(null).forEach(user -> {
            user.setEmail(user.getEmail());
            userMapper.updateById(user);
        });
        System.out.println("批量更新完成");
    }
    /**
     * 测试带条件的乐观锁更新
     */
    @Test
    void testConditionalOptimisticLock() {
        User user = userMapper.selectById(1L);
        user.setEmail("newemail@example.com");
        boolean result = userService.updateByOptimisticLock(user);
        System.out.println("条件更新结果: " + result);
        // 如果成功,查询并打印
        if (result) {
            User updated = userMapper.selectById(1L);
            System.out.println("更新后的数据: " + updated);
        }
    }
}

启动类

package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

SQL日志输出示例

==>  Preparing: SELECT id,name,age,email,version,create_time,update_time FROM user WHERE id=? AND version=?
==> Parameters: 1(Integer), 0(Integer)
<==    Columns: id, name, age, email, version, create_time, update_time
<==        Row: 1, 张三, 20, zhangsan@example.com, 0, 2024-01-01, 2024-01-01
==>  Preparing: UPDATE user SET name=?, age=?, email=?, version=?, update_time=? WHERE id=? AND version=?
==> Parameters: 张三(String), 21(Integer), zhangsan@example.com(String), 1(Integer), 2024-01-01, 1(Long), 0(Integer)

关键点说明

乐观锁实现原理

  • 查询时获取version字段
  • 更新时执行:UPDATE ... WHERE id = ? AND version = ?
  • 更新成功后自动将version + 1

注意事项

  • @Version注解:必须加在版本字段上
  • 更新实体:使用updateById时会自动处理version
  • 更新Wrapper:使用update(UpdateWrapper)时需手动处理version
  • 自动填充:插入时可设置初始version值为0

使用场景

  • 商品库存更新
  • 订单状态流转
  • 用户信息修改
  • 优惠券使用等需要防止并发冲突的业务

这个案例完整展示了MyBatis-Plus乐观锁的配置和使用方法,包含并发场景测试,可以实际运行验证效果。

抱歉,评论功能暂时关闭!