李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
06.MyBatis-Plus插件介绍
Leefs
2022-12-21 PM
1447℃
0条
[TOC] ### 一、MyBatis插件机制 MyBatis插件就是对`Executor`、`StatementHandler`、 `ParameterHandler`、`ResultSetHandler` 这四个接口上的方法进行拦截,利用JDK动态代理机制,为这些接口的实现类创建代理对象, 在执行方法时,先去执行代理对象的方法,从而执行自己编写的拦截逻辑。 **接口说明** | 接口 | 说明 | | ------------------------------------------------------------ | ------------------------------------------------------------ | | `Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)` | MyBatis 的内部执行器,它负责调用 `StatementHandler` 操作数据库,并把结果集通过 `ResultSetHandler` 进行自动映射。 | | `StatementHandler (prepare, parameterize, batch, update, query)` | MyBatis直接让数据库执行 SQL 脚本的对象。 | | `ParameterHandler (getParameterObject, setParameters)` | MyBatis实现 SQL 入参设置的对象。 | | `ResultSetHandler (handleResultSets, handleOutputParameters)` | MyBatis把 `ResultSet` 集合映射成 POJO 的接口对象。 | 我们看到了可以拦截Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。 > 总体概括为: > > 1. 拦截执行器的方法 > 2. 拦截参数的处理 > 3. 拦截结果集的处理 > 4. 拦截SQL语法构建的处理 ### 二、分页插件 #### 2.1 支持的数据库 - mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb - 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库 #### 2.2 集成分页插件 > MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能 **(1)添加配置类** ```java @Configuration @MapperScan("com.lilinchao.mybatisplusdemo.mapper") //可以将主类中的注解移到此处 public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } } ``` **(2)测试** ```java @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()); } ``` **运行结果** ``` ==> Preparing: SELECT COUNT(*) AS total FROM t_user WHERE is_deleted = 0 ==> Parameters: <== Columns: total <== Row: 5 <== Total: 1 ==> Preparing: SELECT id,name,age,email,is_deleted FROM t_user WHERE is_deleted=0 LIMIT ? ==> Parameters: 5(Long) <== Columns: id, name, age, email, is_deleted <== Row: 1, Jone, 18, test1@baomidou.com, 0 <== Row: 2, Jack, 20, test2@baomidou.com, 0 <== Row: 3, Tom, 20, leefs@163.com, 0 <== Row: 4, Sandy, 28, leefs@163.com, 0 <== Row: 5, Billie, 24, test5@baomidou.com, 0 <== Total: 5 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4f5f6e45] 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=20, email=leefs@163.com, isDeleted=0) User(id=4, name=Sandy, age=28, email=leefs@163.com, isDeleted=0) User(id=5, name=Billie, age=24, email=test5@baomidou.com, isDeleted=0) 当前页:1 每页显示的条数:5 总记录数:5 总页数:1 是否有上一页:false 是否有下一页:false ``` #### 2.3 Page > 该类继承了 `IPage` 类,实现了 `简单分页模型` 如果你要实现自己的分页模型可以继承 `Page` 类或者实现 `IPage` 类 | 属性名 | 类型 | 默认值 | 描述 | | :--------------------: | :-----: | :-------: | :----------------------------------------------------------: | | records | List | emptyList | 查询数据列表 | | total | Long | 0 | 查询列表总记录数 | | size | Long | 10 | 每页显示条数,默认 `10` | | current | Long | 1 | 当前页 | | orders | List | emptyList | 排序字段信息,允许前端传入的时候,注意 SQL 注入问题,可以使用 `SqlInjectionUtils.check(...)` 检查文本 | | optimizeCountSql | boolean | true | 自动优化 COUNT SQL 如果遇到 `jSqlParser` 无法解析情况,设置该参数为 `false` | | optimizeJoinOfCountSql | boolean | true | 自动优化 COUNT SQL 是否把 join 查询部分移除 | | searchCount | boolean | true | 是否进行 count 查询,如果指向查询到列表不要查询总记录数,设置该参数为 `false` | | maxLimit | Long | | 单页分页条数限制 | | countId | String | | `xml` 自定义 `count` 查询的 `statementId` | ### 三、xml自定义分页 > 在有些业务场景下,分页插件无法满足我们的需求的时候,这个时候就可以选择自定义分页。 #### 3.1 UserMapper中定义接口方法 ```java /** * 根据年龄查询用户列表,分页显示 * @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位 * @param age 年龄 * @return */ IPage
selectPageVo(@Param("page")Page
page, @Param("age")Integer age); ``` #### 3.2 UserMapper.xml中编写SQL ```xml
id,name,age,email
SELECT
FROM t_user WHERE age > #{age}
``` #### 3.3 测试 ```java @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()); } ``` **运行结果** ``` ==> Preparing: SELECT COUNT(*) AS total FROM t_user WHERE age > ? ==> Parameters: 20(Integer) <== Columns: total <== Row: 3 <== Total: 1 ==> Preparing: SELECT id,name,age,email FROM t_user WHERE age > ? LIMIT ? ==> Parameters: 20(Integer), 5(Long) <== Columns: id, name, age, email <== Row: 4, Sandy, 28, leefs@163.com <== Row: 5, Billie, 24, test5@baomidou.com <== Row: 6, 张三, 33, null <== Total: 3 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5c8e67b9] User(id=4, name=Sandy, age=28, email=leefs@163.com, isDeleted=0) User(id=5, name=Billie, age=24, email=test5@baomidou.com, isDeleted=0) User(id=6, name=张三, age=33, email=null, isDeleted=0) 当前页:1 每页显示的条数:5 总记录数:3 总页数:1 是否有上一页:false 是否有下一页:false ``` ### 四、乐观锁 #### 4.1 场景 > 一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太 高,可能会影响销量。又通知小王,你把商品价格降低30元。 > > 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。 > > 现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1 万多。 #### 4.2 乐观锁与悲观锁 > 上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。 > > 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元。 #### 4.3 模拟修改冲突 **(1)数据库中增加商品表** ```mysql CREATE TABLE t_product ( id BIGINT(20) NOT NULL COMMENT '主键ID', `name` VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称', price INT(11) DEFAULT 0 COMMENT '价格', version INT(11) DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (id) ); ``` **(2)添加数据** ```sql INSERT INTO t_product (id, `name`, price) VALUES (1, '外星人笔记本', 100); ``` **(3)添加实体** ``` @Data public class Product { private Long id; private String name; private Integer price; private Integer version; } ``` **(4)添加mapper** ```java @Repository public interface ProductMapper extends BaseMapper
{ } ``` **(5)测试** ```java @Test public void testConcurrentUpdate() { //1、小李 Product p1 = productMapper.selectById(1L); System.out.println("小李取出的价格:" + p1.getPrice()); //2、小王 Product p2 = productMapper.selectById(1L); System.out.println("小王取出的价格:" + p2.getPrice()); //3、小李将价格加了50元,存入了数据库 p1.setPrice(p1.getPrice() + 50); int result1 = productMapper.updateById(p1); System.out.println("小李修改结果:" + result1); //4、小王将商品减了30元,存入了数据库 p2.setPrice(p2.getPrice() - 30); int result2 = productMapper.updateById(p2); System.out.println("小王修改结果:" + result2); //最后的结果 Product p3 = productMapper.selectById(1L); //价格覆盖,最后的结果:70 System.out.println("最后的结果:" + p3.getPrice()); } ``` **运行结果** ``` ==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=? ==> Parameters: 1(Long) <== Columns: id, name, price, version <== Row: 1, 外星人笔记本, 100, 0 <== Total: 1 小李取出的价格:100 ==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=? ==> Parameters: 1(Long) <== Columns: id, name, price, version <== Row: 1, 外星人笔记本, 100, 0 <== Total: 1 小王取出的价格:100 ==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? ==> Parameters: 外星人笔记本(String), 150(Integer), 0(Integer), 1(Long) <== Updates: 1 小李修改结果:1 ==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? ==> Parameters: 外星人笔记本(String), 70(Integer), 0(Integer), 1(Long) <== Updates: 1 小王修改结果:1 ==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=? ==> Parameters: 1(Long) <== Columns: id, name, price, version <== Row: 1, 外星人笔记本, 70, 0 <== Total: 1 最后的结果:70 ``` #### 4.4 乐观锁实现流程 **(1)首先数据库中要有version字段** ![06.MyBatis-Plus插件介绍01.jpg](https://lilinchao.com/usr/uploads/2022/12/1629692002.jpg) **(2)取出记录时,获取当前version** ```sql SELECT id,`name`,price,`version` FROM product WHERE id=1 ``` **(3)更新时,version + 1,如果where语句中的version版本不对,则更新失败** ```sql UPDATE product SET price=price+50, version=version + 1 WHERE id=1 AND version=1 ``` #### 4.5 Mybatis-Plus实现乐观锁 **(1)修改实体类** ```java @Data public class Product { private Long id; private String name; private Integer price; @Version private Integer version; } ``` **(2)添加乐观锁插件配置** ```java @Configuration @MapperScan("com.lilinchao.mybatisplusdemo.mapper") //可以将主类中的注解移到此处 public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //添加分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //添加乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } } ``` **(3)优化流程** ```java @Test public void testConcurrentVersionUpdate() { //小李取数据 Product p1 = productMapper.selectById(1L); //小王取数据 Product p2 = productMapper.selectById(1L); //小李修改 + 50 p1.setPrice(p1.getPrice() + 50); int result1 = productMapper.updateById(p1); System.out.println("小李修改的结果:" + result1); //小王修改 - 30 p2.setPrice(p2.getPrice() - 30); int result2 = productMapper.updateById(p2); System.out.println("小王修改的结果:" + result2); if(result2 == 0){ //失败重试,重新获取version并更新 p2 = productMapper.selectById(1L); p2.setPrice(p2.getPrice() - 30); result2 = productMapper.updateById(p2); } System.out.println("小王修改重试的结果:" + result2); //老板看价格 Product p3 = productMapper.selectById(1L); System.out.println("老板看价格:" + p3.getPrice()); } ``` **运行结果** ``` ==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=? ==> Parameters: 1(Long) <== Columns: id, name, price, version <== Row: 1, 外星人笔记本, 100, 0 <== Total: 1 ==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=? ==> Parameters: 1(Long) <== Columns: id, name, price, version <== Row: 1, 外星人笔记本, 100, 0 <== Total: 1 ==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=? ==> Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer) <== Updates: 1 小李修改的结果:1 ==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=? ==> Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer) <== Updates: 0 小王修改的结果:0 ==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=? ==> Parameters: 1(Long) <== Columns: id, name, price, version <== Row: 1, 外星人笔记本, 150, 1 <== Total: 1 ==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=? ==> Parameters: 外星人笔记本(String), 120(Integer), 2(Integer), 1(Long), 1(Integer) <== Updates: 1 小王修改重试的结果:1 ==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=? ==> Parameters: 1(Long) <== Columns: id, name, price, version <== Row: 1, 外星人笔记本, 120, 2 <== Total: 1 老板看价格:120 ``` *附参考文章链接地址* *《尚硅谷MyBatisPlus教程》*
标签:
MyBatis
,
MyBatis-Plus
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://www.lilinchao.com/archives/2698.html
上一篇
05.MyBatis-Plus条件构造器和常用接口
下一篇
07.MyBatis-Plus通用枚举和代码生成器
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
标签云
微服务
RSA加解密
持有对象
JavaScript
SpringBoot
机器学习
数学
Quartz
队列
MySQL
Ubuntu
容器深入研究
Java工具类
正则表达式
Java
Docker
散列
Map
Azkaban
并发线程
Kibana
查找
Kafka
ClickHouse
BurpSuite
稀疏数组
Shiro
Sentinel
哈希表
人工智能
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞