MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为 简化开发、提高效率而生。
我们的愿景是成为 MyBatis 最好的搭档,就像魂斗罗中的 1P、2P,基友搭配,效率翻倍。


官方地址: http://mp.baomidou.com
代码发布地址:
Github: https://github.com/baomidou/mybatis-plus Gitee: https://gitee.com/baomidou/mybatis-plus
文档发布地址: https://baomidou.com/pages/24112f
IDE:idea 2019.2
JDK:JDK8+
构建工具:maven 3.5.4
MySQL版本:MySQL 5.7
Spring Boot:2.6.3
MyBatis-Plus:3.5.1
- CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; use `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 '邮箱', PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
- INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'),
- (2, 'Jack', 20, 'test2@baomidou.com'),
- (3, 'Tom', 28, 'test3@baomidou.com'),
- (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');



- <dependencies>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starterartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-testartifactId>
- <scope>testscope>
- dependency>
- <dependency>
- <groupId>com.baomidougroupId>
- <artifactId>mybatis-plus-boot-starterartifactId>
- <version>3.5.1version>
- dependency>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId> <optional>trueoptional>
- dependency>
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- <scope>runtimescope>
- dependency>
- dependencies>

- spring:
- # 配置数据源信息
- datasource:
- # 配置数据源类型
- type: com.zaxxer.hikari.HikariDataSource
- # 配置连接数据库信息
- driver-class-name: com.mysql.cj.jdbc.Driver
- url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
- username: root
- password: 123456
注意:
1、驱动类driver-class-name
spring boot 2.0(内置jdbc5驱动),驱动类使用:driver-class-name: com.mysql.jdbc.Driver
spring boot 2.1及以上(内置jdbc8驱动),驱动类使用:driver-class-name:com.mysql.cj.jdbc.Driver
否则运行测试用例的时候会有 WARN 信息
2、连接地址url
MySQL5.7版本的url:
jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
MySQL8.0版本的url:
jdbc:mysql://localhost:3306/mybatis_plus? serverTimezone=GMT%2B8characterEncoding=utf-8&useSSL=false
否则运行测试用例报告如下错误: java.sql.SQLException: The server time zone value 'ÖÐ1ú±ê×1⁄4ʱ1⁄4ä' is unrecognized or represents more
在Spring Boot启动类中添加@MapperScan注解,扫描mapper包
- @SpringBootApplication @MapperScan("com.atguigu.mybatisplus.mapper")
- public class MybatisplusApplication {
- public static void main(String[] args){
- SpringApplication.run(MybatisplusApplication.class, args);
- }
- }
- @Data //lombok注解 public class User {
- private Long id;
- private String name;
- private Integer age;
- private String email;
- }
BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的实体类型
- public interface UserMapper extends BaseMapper
{ - }
- @SpringBootTest
- public class MybatisPlusTest {
- @Autowired
- private UserMapper userMapper;
- @Test
- public void testSelectList(){ //selectList()根据MP内置的条件构造器查询一个list集合,null表示没有条件,即查询所有
- userMapper.selectList(null).forEach(System.out::println);
- }
- }
注意:
IDEA在 userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确的执行。
为了避免报错,可以在mapper接口上添加 @Repository 注解
在application.yml中配置日志输出
- # 配置MyBatis日志
- mybatis-plus:
- configuration:
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用,接口如 下:
- package com.baomidou.mybatisplus.core.mapper;
- public interface BaseMapper
extends Mapper { - /**
- * 插入一条记录
- * @param entity 实体对象 */
- int insert(T entity);
- /**
- * 根据 ID 删除
- * @param id 主键ID */
- int deleteById(Serializable id);
- /**
- * 根据实体(ID)删除
- * @param entity 实体对象 * @since 3.4.4
- */
- int deleteById(T entity);
- /**
- * 根据 columnMap 条件,删除记录
- * @param columnMap 表字段 map 对象 */
- int deleteByMap(@Param(Constants.COLUMN_MAP) Map
columnMap) ; - /**
- * 根据 entity 条件,删除记录
- * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生的where语句)
- */
- int delete(@Param(Constants.WRAPPER) Wrapper
queryWrapper) ; - /**
- * 删除(根据ID 批量删除)
- * @param idList 主键ID列表(不能为 null 以及 empty) */
- int deleteBatchIds(@Param(Constants.COLLECTION) Collection extends Serializable> idList);
- /**
- * 根据 ID 修改
- * @param entity 实体对象 */
- int updateById(@Param(Constants.ENTITY) T entity);
- /**
- * 根据 whereEntity 条件,更新记录
- * @param entity 实体对象 (set 条件值,可以为 null)
- * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成
- where 语句) */
- int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper
updateWrapper) ; - /**
- * 根据 ID 查询
- * @param id 主键ID */
- T selectById(Serializable id);
- /**
- * 查询(根据ID 批量查询)
- * @param idList 主键ID列表(不能为 null 以及 empty) */
- List
selectBatchIds(@Param(Constants.COLLECTION) Collection extends Serializable> idList); - /**
- * 查询(根据 columnMap 条件)
- * @param columnMap 表字段 map 对象 */
- List
selectByMap(@Param(Constants.COLUMN_MAP) Map columnMap) ; - /**
- * 根据 entity 条件,查询一条记录
- *
查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报
- * @param queryWrapper 实体对象封装操作类(可以为 null)
- */
- default T selectOne(@Param(Constants.WRAPPER) Wrapper
queryWrapper) { - List
ts = this.selectList(queryWrapper); - if (CollectionUtils.isNotEmpty(ts)) {
- if (ts.size() != 1) {
- throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records");
- }
- return ts.get(0);
- }
- return null;
- }
- /**
- * 根据 Wrapper 条件,查询总记录数
- * @param queryWrapper 实体对象封装操作类(可以为 null) */
- Long selectCount(@Param(Constants.WRAPPER) Wrapper
queryWrapper) ; - /**
- * 根据 entity 条件,查询全部记录
- * @param queryWrapper 实体对象封装操作类(可以为 null) */
- List
selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper) ; - /**
- * 根据 Wrapper 条件,查询全部记录
- * @param queryWrapper 实体对象封装操作类(可以为 null) */
- List
- /**
- * 根据 Wrapper 条件,查询全部记录
- *
注意: 只返回第一个字段的值
- * @param queryWrapper 实体对象封装操作类(可以为 null) */
- List
- }
- @Test
- public void testInsert(){
- User user = new User(null, "张三", 23, "zhangsan@atguigu.com");
- //INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
- int result = userMapper.insert(user);
- System.out.println("受影响行数:"+result);
- //1475754982694199298
- System.out.println("id自动获取:"+user.getId());
- }
最终执行的结果,所获取的id为1475754982694199298 ,这是因为MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id
- @Test
- public void testDeleteById(){
- //通过id删除用户信息
- //DELETE FROM user WHERE id=?
- int result = userMapper.deleteById(1475754982694199298L); System.out.println("受影响行数:"+result);
- }
- @Test
- public void testDeleteBatchIds(){ //通过多个id批量删除
- //DELETE FROM user WHERE id IN ( ? , ? , ? )
- List
idList = Arrays.asList(1L, 2L, 3L); - int result = userMapper.deleteBatchIds(idList);
- System.out.println("受影响行数:"+result);
- }
- @Test
- public void testDeleteByMap(){
- //根据map集合中所设置的条件删除记录
- //DELETE FROM user WHERE name = ? AND age = ?
- Map
map = new HashMap<>(); - map.put("age", 23);
- map.put("name", "张三");
- int result = userMapper.deleteByMap(map);
- System.out.println("受影响行数:"+result);
- }
- @Test
- public void testUpdateById(){
- User user = new User(4L, "admin", 22, null);
- //UPDATE user SET name=?, age=? WHERE id=?
- int result = userMapper.updateById(user);
- System.out.println("受影响行数:"+result);
- }
- @Test
- public void testSelectById(){
- //根据id查询用户信息
- //SELECT id,name,age,email FROM user WHERE id=?
- User user = userMapper.selectById(4L);
- System.out.println(user);
- }
- @Test
- public void testSelectBatchIds(){
- //根据多个id查询多个用户信息
- //SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
- List
idList = Arrays.asList(4L, 5L); - List
list = userMapper.selectBatchIds(idList); - list.forEach(System.out::println);
- }
- @Test
- public void testSelectByMap(){
- //通过map条件查询用户信息
- //SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
- Map
map = new HashMap<>(); - map.put("age", 22);
- map.put("name", "admin");
- List
list = userMapper.selectByMap(map); - list.forEach(System.out::println);
- }
- @Test
- public void testSelectList(){ //查询所有用户信息
- //SELECT id,name,age,email FROM user
- List
list = userMapper.selectList(null); - list.forEach(System.out::println);
- }
通过观察BaseMapper中的方法,大多方法中都有Wrapper类型的形参,此为条件构造器,可针 对于SQL语句设置不同的条件,若没有条件,则可以为该形参赋值null,即查询(删除/修改)所 有数据。
说明:
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑 详情查看源码IService和ServiceImpl
- /**
- * UserService继承IService模板提供的基础功能 */
- public interface UserService extends IService
{ - }
- /**
- * ServiceImpl实现了IService,提供了IService中基础功能的实现
- * 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现 */
- @Service
- public class UserServiceImpl extends ServiceImpl
implements - UserService {
- }
- @Autowired
- private UserService userService;
- @Test
- public void testGetCount(){
- long count = userService.count(); System.out.println("总记录数:" + count);
- }
- @Test
- public void testSaveBatch(){
- // SQL长度有限制,海量数据插入单条SQL无法实行,
- // 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
- ArrayList
users = new ArrayList<>(); - for (int i = 0; i < 5; i++) {
- User user = new User(); user.setName("ybc" + i);
- user.setAge(20 + i);
- users.add(user);
- }
- //SQL:INSERT INTO t_user ( username, age ) VALUES ( ?, ? )
- userService.saveBatch(users);
- }
经过以上的测试,在使用MyBatis-Plus实现基本的CRUD时,我们并没有指定要操作的表,只是在 Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为user表
由此得出结论,MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定,即实体类型决 定,且默认操作的表名和实体类型的类名一致
若实体类类型的类名和要操作的表的表名不一致,会出现什么问题?
我们将表user更名为t_user,测试查询功能程序抛出异常,Table 'mybatis_plus.user' doesn't exist,因为现在的表名为t_user,而默认操作的表名和实体类型的类名一致,即user表

在实体类类型上添加@TableName("t_user"),标识实体类对应的表,即可成功执行SQL语句

在开发的过程中,我们经常遇到以上的问题,即实体类所对应的表都有固定的前缀,例如t_或tbl_,此时,可以使用MyBatis-Plus提供的全局配置,为实体类所对应的表名设置默认的前缀,那么就 不需要在每个实体类上通过@TableName标识实体类对应的表
- mybatis-plus:
- configuration:
- # 配置MyBatis日志
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- global-config:
- db-config:
- # 配置MyBatis-Plus操作表的默认前缀
- table-prefix: t_
经过以上的测试,MyBatis-Plus在实现CRUD时,会默认将id作为主键列,并在插入数据时,默认基于雪花算法的策略生成id
若实体类和表中表示主键的不是id,而是其他字段,例如uid,MyBatis-Plus会自动识别uid为主 键列吗?
我们实体类中的属性id改为uid,将表中的字段id也改为uid,测试添加功能程序抛出常,Field 'uid' doesn't have a default value,说明MyBatis-Plus没有将uid作为主键 赋值

在实体类中uid属性上通过@TableId将其标识为主键,即可成功执行SQL语句

若实体类中主键对应的属性为id,而表中表示主键的字段为uid,此时若只在属性id上添加注解 @TableId,则抛出异常Unknown column 'id' in 'field list',即MyBatis-Plus仍然会将id作为表的 主键操作,而表中表示主键的是字段uid,此时需要通过@TableId注解的value属性,指定表中的主键字段,@TableId("uid")或 @TableId(value="uid")

type属性用来定义主键策略
常用的主键策略:
| 值 | 描述 |
| IdType.ASSIGN_ID(默 认) | 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关 |
| IdType.AUTO | 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增, 否则无效 |
配置全局主键策略:
- mybatis-plus:
- configuration:
- # 配置MyBatis日志
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
- global-config:
- db-config:
- # 配置MyBatis-Plus操作表的默认前缀
- table-prefix: t_
- # 配置MyBatis-Plus的主键策略
- id-type: auto
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的 主键的有序性。
核心思想:
长度共64bit(一个long型)。
首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负 数是1,所以id一般是正数,最高位是0。
41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。
10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。

优点:
整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
经过以上的测试,我们可以发现,MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和 表中的字段名一致,如果实体类中的属性名和字段名不一致的情况,会出现什么问题呢?
若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格,例如实体类属性userName,表中字段user_name 此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格 相当于在MyBatis中配置
若实体类中的属性和表中的字段不满足情况1 例如实体类属性name,表中字段username 此时需要在实体类属性上使用@TableField("username")设置属性所对应的字段名

step1: 数据库中创建逻辑删除状态列,设置默认值为0
step2: 实体类中添加逻辑删除属性

step3: 测试
测试删除功能,真正执行的是修改
UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0 测试查询功能,被逻辑删除的数据默认不会被查询
SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0

Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper LambdaUpdateWrapper : Lambda 更新封装Wrapper
- @Test
- public void test01(){
- //查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
- //SELECT id,username AS name,age,email,is_deleted FROM t_user WHEREis_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.like("username", "a")
- .between("age", 20, 30)
- .isNotNull("email");
- List
list = userMapper.selectList(queryWrapper); - list.forEach(System.out::println);
- }
- @Test
- public void test02(){
- //按年龄降序查询用户,如果年龄相同则按id升序排列
- //SELECT id,username AS name,age,email,is_deleted FROM t_user WHEREis_deleted=0 ORDER BY age DESC,id ASC
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper
- .orderByDesc("age")
- .orderByAsc("id");
- List
users = userMapper.selectList(queryWrapper); - users.forEach(System.out::println);
- }
- @Test
- public void test03(){
- //删除email为空的用户
//DELETE FROM t_user WHERE (email IS NULL)
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.isNull("email"); //条件构造器也可以构建删除语句的条件
- int result = userMapper.delete(queryWrapper);
- System.out.println("受影响的行数:" + result);
- }
- @Test
- public void test04() {
- QueryWrapper
queryWrapper = new QueryWrapper<>(); //将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改 - //UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND age > ? OR email IS NULL)
- queryWrapper
- .like("username", "a")
- .gt("age", 20) .or()
- .isNull("email");
- User user = new User();
- user.setAge(18); user.setEmail("user@atguigu.com");
- int result = userMapper.update(user, queryWrapper);
- System.out.println("受影响的行数:" + result);
- }
- @Test
- public void test05() {
- //查询用户信息的username和age字段
- //SELECT username,age FROM t_user
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.select("username", "age");
- //selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
- List
- maps.forEach(System.out::println);
- }
- @Test
- public void test06() {
- //查询id小于等于3的用户信息
- //SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (id IN (select id from t_user where id <= 3))
- QueryWrapper
queryWrapper = new QueryWrapper<>(); - queryWrapper.inSql("id", "select id from t_user where id <= 3");
- List
list = userMapper.selectList(queryWrapper); - list.forEach(System.out::println);
- }
- @Test
- public void test07() {
- //将(年龄大于20或邮箱为null)并且用户名中包含有a的用户信息修改
- //组装set子句以及修改条件
- UpdateWrapper
updateWrapper = new UpdateWrapper<>(); - //lambda表达式内的逻辑优先运算
- updateWrapper
- .set("age", 18)
-
.set("email", "user@atguigu.com")
- .like("username", "a")
- .and(i -> i.gt("age", 20).or().isNull("email"));
- //这里必须要创建User对象,否则无法应用自动填充。如果没有自动填充,可以设置为null
- //UPDATE t_user SET username=?, age=?,email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
- //User user = new User();
- //user.setName("张三");
- //int result = userMapper.update(user, updateWrapper);
- //UPDATE t_user SET age=?,email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
- int result = userMapper.update(null, updateWrapper);
- System.out.println(result);
- }
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
- @Configuration
- @MapperScan("com.atguigu.mybatisplus.mapper") //可以将主类中的注解移到此处
- public class MybatisPlusConfig {
- @Bean
- public MybatisPlusInterceptor mybatisPlusInterceptor() {
- MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
- interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
- return interceptor;
- }
- }
- @Test
- public void testPage(){
- //设置分页参数
- Page
page = new Page<>(1, 5); - userMapper.selectPage(page, null);
- //获取分页数据
- List
list = page.getRecords(); - list.forEach(System.out::println);
- System.out.println("当前页:"+page.getCurrent());
- System.out.println("每页显示的条数:"+page.getSize());
- System.out.println("总记录数:"+page.getTotal());
- System.out.println("总页数:"+page.getPages());
- System.out.println("是否有上一页:"+page.hasPrevious());
- System.out.println("是否有下一页:"+page.hasNext());
- }
测试结果:
User(id=1, name=Jone, age=18, email=test1@baomidou.com, isDeleted=0) User(id=2, name=Jack, age=20, email=test2@baomidou.com, isDeleted=0) User(id=3, name=Tom, age=28, email=test3@baomidou.com, isDeleted=0) User(id=4, name=Sandy, age=21, email=test4@baomidou.com, isDeleted=0) User(id=5, name=Billie, age=24, email=test5@ba omidou.com, isDeleted=0) 当前页:1 每页显示的条数:5 总记录数:17 总页数:4 是否有上一 页:false 是否有下一页:true
- /**
- * 根据年龄查询用户列表,分页显示
- * @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位 * @param age 年龄
- * @return
- */
- I
- Page
selectPageVo(@Param("page") Page page, @Param("age") - Integer age);
- <sql id="BaseColumns">id,username,age,emailsql>
- <select id="selectPageVo" resultType="User">
- SELECT <include refid="BaseColumns">include>
- FROM t_user WHERE age > #{age}
- select>
- @Test
- public void testSelectPageVo(){
- //设置分页参数
- Page
page = new Page<>(1, 5); userMapper.selectPageVo(page, 20); - //获取分页数据
- List
list = page.getRecords(); - list.forEach(System.out::println);
- System.out.println("当前页:"+page.getCurrent());
- System.out.println("每页显示的条数:"+page.getSize());
- System.out.println("总记录数:"+page.getTotal());
- System.out.println("总页数:"+page.getPages());
- System.out.println("是否有上一页:"+page.hasPrevious());
- System.out.println("是否有下一页:"+page.hasNext());
- }
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小 李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太 高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王 也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据 库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就 完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1 万多。
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元。
数据库中添加version字段
取出记录时,获取当前version
SELECT id,`name`,price,`version` FROM product WHERE id=1
更新时,version + 1,如果where语句中的version版本不对,则更新失败
UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1
修改实体类
- import com.baomidou.mybatisplus.annotation.Version;
- import lombok.Data;
- @Data
- public class Product {
- private Long id;
- private String name;
- private Integer price;
- @Version
- private Integer version;
- }
添加乐观锁插件配置
- @Configuration
- @MapperScan("com.atguigu.mybatisplus.mapper") //可以将主类中的注解移到此处
- public class MybatisPlusConfig {
- @Bean
- public MybatisPlusInterceptor mybatisPlusInterceptor() {
- MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
- //添加分页插件
- interceptor.addInnerInterceptor(new
- PaginationInnerInterceptor(DbType.MYSQL));
- //添加乐观锁插件
- interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
- return interceptor;
- }
- }
- <dependency>
- <groupId>com.baomidougroupId>
- <artifactId>mybatis-plus-generatorartifactId>
- <version>3.5.1version>
- dependency>
- <dependency>
- <groupId>org.freemarkergroupId>
- <artifactId>freemarkerartifactId>
- <version>2.3.31version>
- dependency>
- public static void main(String[] args) {
- FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false", "root", "123456")
- .globalConfig(builder -> {
- builder.author("atguigu") // 设置作者
- //.enableSwagger() // 开启 swagger 模式
- .fileOverride() // 覆盖已生成文件
- .outputDir("D://mybatis_plus"); // 指定输出目录
- })
- .packageConfig(builder -> {
- builder.parent("com.atguigu") // 设置父包名
- .moduleName("mybatisplus") // 设置父包模块名
- .pathInfo(Collections.singletonMap(OutputFile.mapperXml,"D://mybatis_plus")); // 设置mapperXml生成路径
- })
- .strategyConfig(builder -> {
- builder.addInclude("t_user") // 设置需要生成的表名
- .addTablePrefix("t_","c_"); // 设置过滤表前缀
- })
- .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
- .execute();
- }
- }