一、前言 MybatisPlusInterceptor MybatisPlusInterceptor:该插件是核心插件,目前代理了 Executor#query
和 Executor#update
和 StatementHandler#prepare
方法。
官方文档:插件使用
InnerInterceptor 我们提供的插件都将基于此接口来实现功能
目前已有的功能:
自动分页: PaginationInnerInterceptor 多租户: TenantLineInnerInterceptor 动态表名: DynamicTableNameInnerInterceptor 乐观锁: OptimisticLockerInnerInterceptor sql 性能规范: IllegalSQLInnerInterceptor 防止全表更新与删除: BlockAttackInnerInterceptor 注意:使用多个功能需要注意顺序关系,建议使用如下顺序
多租户,动态表名 分页,乐观锁 sql 性能规范,防止全表更新与删除 总结: 对 sql 进行单次改造的优先放入,不对 sql 进行改造的最后放入
二、分页插件 MyBatis Plus 自带分页插件,只要简单的配置即可实现分页功能
1、添加配置类 创建config 包 ,创建MybatisPlusConfig 类
类中添加 @Bean
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @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; } }
2、测试分页 创建类InterceptorTests
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @SpringBootTest public class InterceptorTests { @Resource private UserMapper userMapper; @Test public void testSelectPage () { Page<User> pageParam = new Page<>(1 , 5 ); userMapper.selectPage(pageParam, null ); List<User> users = pageParam.getRecords(); users.forEach(System.out::println); } }
3、XML 自定义分页 UserMapper
中定义接口方法,实现自定义分页功能
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.atguigu.mybatisplus.mapper;public interface UserMapper extends BaseMapper <User > { IPage<User> selectPageByAge (Page<?> page, Integer age) ; }
定义 XML
1 2 3 4 5 6 7 8 9 10 <sql id ="Base_Column_List" > uid,name,age,email,is_deleted,create_time,update_time </sql > <select id ="selectPageByAge" resultType ="com.atguigu.mybatisplus.entity.User" > select <include refid ="Base_Column_List" /> from t_user where age > #{age} </select >
测试(总记录数为 0,MP 不进行分页查询,比如查询年龄大于 200 的)
1 2 3 4 5 6 7 8 9 10 @Test public void testSelectPageByAge () { Page<User> userParam = new Page<>(1 , 5 ); userMapper.selectPageByAge(userParam, 20 ); List<User> users = userParam.getRecords(); users.forEach(System.out::println); }
4、补充 字段映射的两种解决方法
在UserMapper
中使用resultMap
做映射
1 2 3 4 <resultMap id ="myUser" type ="com.atguigu.mybatisplus.entity.User" > <id column ="uid" property ="id" /> </resultMap >
或者直接起别名
1 2 3 4 5 6 7 8 9 <sql id ="Base_Column_List" > uid as id, name, age, email, is_deleted as deleted, create_time as createTime, update_time as updateTime </sql >
三、乐观锁 1、场景 一件商品,成本价是 80 元,售价是 100 元。老板先是通知小李,说你去把商品价格增加 50 元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到 150 元,价格太高,可能会影响销量。又通知小王,你把商品价格降低 30 元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格 100 元;小王也在操作,取出的商品价格也是 100 元。小李将价格加了 50 元,并将 100+50=150 元存入了数据库;小王将商品减了 30 元,并将 100-30=70 元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是 70 元,比成本价低 10 元。几分钟后,这个商品很快出售了 1 千多件商品,老板亏 1 万多。
接下来将我们演示这一过程:
第一步:数据库中增加商品表
1 2 3 4 5 6 7 8 9 10 CREATE TABLE product( id BIGINT (20 ) NOT NULL AUTO_INCREMENT COMMENT '主键ID' , name VARCHAR (30 ) NULL DEFAULT NULL COMMENT '商品名称' , price INT (11 ) DEFAULT 0 COMMENT '价格' , version INT (11 ) DEFAULT 0 COMMENT '乐观锁版本号' , PRIMARY KEY (id) ); INSERT INTO product (id, NAME, price) VALUES (1 , '笔记本' , 100 );
第二步:创建实体类
1 2 3 4 5 6 7 8 9 package com.atguigu.mybatisplus.entity;@Data public class Product { private Long id; private String name; private Integer price; private Integer version; }
第三步:创建 Mapper
1 2 3 4 5 package com.atguigu.mybatisplus.mapper;public interface ProductMapper extends BaseMapper <Product > {}
第四步:测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Resource private ProductMapper productMapper;@Test public void testConcurrentUpdate () { Product p1 = productMapper.selectById(1l ); Product p2 = productMapper.selectById(1L ); p1.setPrice(p1.getPrice() + 50 ); productMapper.updateById(p1); System.out.println("小李修改的结果" + p1.getPrice()); p2.setPrice(p2.getPrice() - 30 ); productMapper.updateById(p2); System.out.println("小王修改的结果" + p2.getPrice()); Product p3 = productMapper.selectById(1L ); System.out.println("老板看到的价格" + p3.getPrice()); }
2、乐观锁方案原理 数据库中添加 version 字段:取出记录时,获取当前 version 更新时,version + 1,如果 where 语句中的 version 版本不对,则更新失败 3、乐观锁实现流程 第一步:修改实体类
在version
属性上添加 @Version
注解
1 2 3 4 5 6 7 8 9 @Data public class Product { private Long id; private String name; private Integer price; @Version private Integer version; }
第二步:在配置类里添加乐观锁插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @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; }
重新执行测试
小王的修改失败,老板看到价格为 150
4、优化流程 失败后重试
1 2 3 4 5 6 7 8 9 10 11 12 13 p2.setPrice(p2.getPrice() - 30 ); int result = productMapper.updateById(p2);System.out.println("小王修改的结果" + p2.getPrice()); if (result == 0 ) { p2 = productMapper.selectById(1L ); p2.setPrice(p2.getPrice() - 30 ); productMapper.updateById(p2); }
测试,最后老板看到的价格 120