为什么要用锁
原因是当两个线程并发修改同一条数据时候
例如有条数据 id 1 count(金额/数量) 500
有两个线程都在查询数据库 查出来都是 1 500
现在两个线程都要修改这条数据 在原来基础上+20 和+30
那么理论来讲应该是550
可是实际有可能是530
原因就是查出来时候都是500
第一个线程加了20 变成520
可是第二个线程查出来也是500 加了30
并发安全问题不是次次发生,是偶尔很小几率发生的事,但是这个几率是确实存在的
Mybatis-plus通过乐观锁可以对这个问题进行处理
加了一个version 版本号
每次更新的时候会将版本号自动+1
当两个线程都在查 查出来 1 500
第一个线程去update的时候 顺手将版本号加了1
例如 update t_test set count=520,set version+=1 where id=xxxxx and version=x
而此时第二个线程去update的时候 由于原先查出来版本号还是原来的 会导致更新不成功 那么在事务的驱动下,全部回滚
官方文档:乐观锁插件 | MyBatis-Plus
关于使用版本 文档上写着SpringBoot2 和SpringBoot3 分别使用的版本
示例demo
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>
乐观锁配置
package com.example.demo.config;
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;
/**
* @author hrui
* @date 2024/8/6 2:38
*/
@Configuration
@MapperScan({"com.example.demo.mapper"})
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
持久层
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.pojo.TTest;
/**
* @author hrui
* @date 2024/8/6 2:39
*/
public interface TTestMapper extends BaseMapper<TTest> {
}
业务接口
package com.example.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.pojo.TTest;
/**
* @author hrui
* @date 2024/8/6 2:41
*/
public interface TTestService extends IService<TTest> {
}
业务实现
package com.example.demo.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.mapper.TTestMapper;
import com.example.demo.pojo.TTest;
import org.springframework.stereotype.Service;
/**
* @author hrui
* @date 2024/8/6 2:43
*/
@Service
public class TTestServiceImpl extends ServiceImpl<TTestMapper, TTest> implements TTestService {
}
实体类
package com.example.demo.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author hrui
* @date 2024/8/6 2:40
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TTest {
private Long id;
private Integer count;
@Version
private Integer version;
}
application.properties
spring.application.name=demo
# 数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=xxxx
spring.datasource.password=xxxxxx
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis-Plus 配置
mybatis-plus.mapper-locations=classpath:/mappers/**/*.xml
#配置实体类包
mybatis-plus.type-aliases-package=com.example.demo.pojo
# 开启驼峰命名转换
mybatis-plus.configuration.map-underscore-to-camel-case=true
# 打印执行的 SQL 语句
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
测试类
package com.example.demo;
import com.example.demo.mapper.TTestMapper;
import com.example.demo.pojo.TTest;
import com.example.demo.service.TTestService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads() {
}
@Autowired
private TTestService tTestService;
@Autowired
private TTestMapper tTestMapper;
@Test
public void test(){
boolean save = tTestService.save(new TTest(null, 200, null));
System.out.println(save);
}
@Test
public void test2(){
TTest byId = tTestService.getById(1820548023958319106L);
TTest byId2 = tTestService.getById(1820548023958319106L);
byId.setCount(201);
byId2.setCount(202);
tTestService.updateById(byId);
tTestService.updateById(byId2);
}
}