本文目录导读:

- 数据库表结构
- Maven依赖
- 配置文件 (application.yml)
- 实体类
- 配置乐观锁插件
- 自定义MetaObjectHandler(自动填充)
- Mapper接口
- Service层
- Service实现类
- 测试类
- 启动类
- SQL日志输出示例
- 关键点说明
我来为你提供一个完整的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乐观锁的配置和使用方法,包含并发场景测试,可以实际运行验证效果。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播