李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
06.MyBatis-Plus插件介绍
Leefs
2022-12-21 PM
2491℃
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://lilinchao.com/archives/2698.html
上一篇
05.MyBatis-Plus条件构造器和常用接口
下一篇
07.MyBatis-Plus通用枚举和代码生成器
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
VUE
NIO
数据结构和算法
递归
设计模式
Kibana
Elasticsearch
Hbase
HDFS
Java工具类
Beego
JavaSE
队列
DataX
Docker
Thymeleaf
LeetCode刷题
Flink
Sentinel
栈
SQL练习题
序列化和反序列化
CentOS
Java阻塞队列
Golang
持有对象
MyBatis-Plus
Hadoop
Scala
ClickHouse
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭