MyBatis-Plus常用插件
# 常用插件
# 1. 分页插件
MyBatis-Plus 自带分页插件,只需简单配置即可实现分页功能。该插件支持常见的分页操作,如构造分页对象、获取分页信息、排序等。
# 1.1 配置分页插件
首先需要配置分页插件,在配置类中添加以下内容:
@Configuration
@MapperScan("com.example.project.mapper") // 指定Mapper接口的扫描路径
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件,指定数据库类型为 MySQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
2
3
4
5
6
7
8
9
10
11
12
API 说明:
PaginationInnerInterceptor(DbType dbType)
:设置分页插件的数据库类型,支持多种数据库,如 MySQL、Oracle 等。
# 1.2 分页查询的基本使用
在 MyBatis-Plus 中,分页查询主要通过 Page<T>
对象来实现。示例如下:
@Test
public void testPage() {
// 创建分页对象,指定当前页和每页显示的记录数量
Page<User> page = new Page<>(1, 5); // 当前页为1,每页显示5条记录
// 构建查询条件(可选)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", 18); // 查询年龄大于等于18的用户
// 执行分页查询
IPage<User> userPage = userMapper.selectPage(page, queryWrapper);
// 获取分页信息
long total = userPage.getTotal(); // 总记录数
long currentPage = userPage.getCurrent(); // 当前页码
long pageSize = userPage.getSize(); // 每页显示的记录数
long totalPages = userPage.getPages(); // 总页数
// 获取查询结果
List<User> users = userPage.getRecords(); // 查询到的记录列表
users.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
重要 API 说明:
Page<T>(long current, long size)
:创建分页对象,current
表示当前页码,size
表示每页显示的记录数。selectPage(Page<T> page, Wrapper<T> queryWrapper)
:执行分页查询,queryWrapper
用于指定查询条件。
# 1.3 常用分页信息
分页对象 Page<T>
提供了多种分页信息和操作方法:
getCurrent()
:获取当前页码。getSize()
:获取每页显示的记录数量。getTotal()
:获取总记录数。getPages()
:获取总页数。getRecords()
:获取查询到的记录列表。hasPrevious()
:判断是否有上一页。hasNext()
:判断是否有下一页。
# 2. 自定义分页
在 MyBatis-Plus 中,尽管内置的 selectPage
方法已经能够满足大部分分页需求,但在一些复杂的业务场景中,我们可能需要自定义分页查询逻辑。自定义分页主要通过在 Mapper 接口中定义方法、编写 SQL 语句、以及调用自定义方法来实现。
# 1. 在 Mapper 接口中自定义分页方法
在 Mapper 接口中定义一个方法,该方法的第一个参数必须是 Page<T>
对象,用于传递分页信息。其他参数则根据业务需求设置。
/**
* 根据年龄分页查询用户列表
* @param page 分页对象,必须放在第一个参数位置,MyBatis-Plus 会自动处理分页逻辑
* @param age 年龄条件,用于过滤用户
* @return 返回分页结果,包含查询到的记录和分页信息
*/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
2
3
4
5
6
7
关键点:
- 分页对象
Page<T>
必须作为第一个参数:这样 MyBatis-Plus 能够自动处理分页逻辑。@Param
注解:用于将方法参数传递给 Mapper.xml 中的 SQL 语句。
# 2. 在 Mapper.xml 文件中编写对应的 SQL 语句
在 Mapper.xml 文件中,编写自定义 SQL 查询语句,实现业务逻辑。确保在 SQL 中处理分页条件。
<select id="selectPageVo" resultType="User">
SELECT id, username, age, email
FROM t_user
WHERE age > #{age} <!-- 根据传入的年龄条件进行过滤 -->
</select>
2
3
4
5
关键点:
resultType="User"
:指定返回结果的类型为User
实体类。#{age}
:使用参数age
进行查询条件过滤。
# 3. 调用自定义分页方法
在 Service 或 Controller 层中调用自定义的分页方法,传递分页参数和查询条件。
@Test
public void testCustomPage() {
// 创建分页对象,指定当前页码和每页显示的记录数
Page<User> page = new Page<>(1, 2); // 当前页为 1,每页显示 2 条记录
// 调用自定义分页方法,传入分页对象和查询条件
Page<User> userPage = userMapper.selectPageVo(page, 20);
// 获取分页结果中的查询记录
List<User> users = userPage.getRecords();
users.forEach(System.out::println);
// 获取分页信息
System.out.println("总记录数:" + userPage.getTotal());
System.out.println("总页数:" + userPage.getPages());
System.out.println("当前页码:" + userPage.getCurrent());
System.out.println("每页记录数:" + userPage.getSize());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
关键点:
- 分页对象的构造:
new Page<>(1, 2)
,其中1
表示当前页,2
表示每页显示 2 条记录。selectPageVo
方法的调用:传入分页对象和查询条件,返回的Page<User>
对象包含查询结果和分页信息。
小结
自定义分页在复杂业务场景中非常有用,虽然 MyBatis-Plus 自带的分页功能已经很强大,但通过自定义方法,可以实现更灵活的分页查询。关键在于分页对象的使用、SQL 语句的编写,以及方法的合理调用。
- 确保分页对象
Page<T>
作为第一个参数传入,这样 MyBatis-Plus 能够自动处理分页逻辑。 - 在 SQL 中合理使用条件和分页参数,以确保查询结果的正确性。
# 3. 乐观锁
作用:当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁的实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
# 3.1 场景
- 一件商品,成本价是 80 元,售价是 100 元。老板先是通知小李,说你去把商品价格增加 50 元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到 150 元,价格太高,可能会影响销量。又通知小王,你把商品价格降低 30 元。
- 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格 100 元;小王也在操作,取出的商品价格也是 100 元。小李将价格加了 50 元,并将 100+50=150 元存入了数据库;小王将商品减了 30 元,并将 100-30=70 元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
- 现在商品价格是 70 元,比成本价低 10 元。几分钟后,这个商品很快出售了 1 千多件商品,老板亏 1 万多。
# 3.2 乐观锁与悲观锁
- 上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150 元,这样他会将 120 元存入数据库。
- 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是 120 元。
# 3.3 模拟修改冲突
数据库中增加商品表
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) );
1
2
3
4
5
6
7添加一条数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
1添加一个实体类
Product
@Data public class Product { private Long id; private String name; private Integer price; private Integer version; }
1
2
3
4
5
6
7添加一个 Mapper 接口
ProductMapper
public interface ProductMapper extends BaseMapper<Product> {}
1测试方法
@Test public void testProduct01(){ //1.小李获取商品价格 Product productLi = productMapper.selectById(1); System.out.println("小李获取的商品价格为:" + productLi.getPrice()); //2.小王获取商品价格 Product productWang = productMapper.selectById(1); System.out.println("小李获取的商品价格为:" + productWang.getPrice()); //3.小李修改商品价格+50 productLi.setPrice(productLi.getPrice()+50); productMapper.updateById(productLi); //4.小王修改商品价格-30 productWang.setPrice(productWang.getPrice()-30); productMapper.updateById(productWang); //5.老板查询商品价格 Product productBoss = productMapper.selectById(1); System.out.println("老板获取的商品价格为:" + productBoss.getPrice()); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22执行结果
# 3.4 乐观锁解决问题
实体类
version
字段添加注解@Version
@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添加乐观锁插件配置
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //添加分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //添加乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; }
1
2
3
4
5
6
7
8
9再次执行测试方法
小李查询商品信息:
SELECT id,name,price,version FROM t_product WHERE id=?
小王查询商品信息:
SELECT id,name,price,version FROM t_product WHERE id=?
小李修改商品价格,自动将 version+1
UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)
小王修改商品价格,此时 version 已更新,条件不成立,修改失败
UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)
最终,小王修改失败,查询价格:150
SELECT id,name,price,version FROM t_product WHERE id=?
优化执行流程
@Test public void testProduct01(){ //1.小李获取商品价格 Product productLi = productMapper.selectById(1); System.out.println("小李获取的商品价格为:" + productLi.getPrice()); //2.小王获取商品价格 Product productWang = productMapper.selectById(1); System.out.println("小李获取的商品价格为:" + productWang.getPrice()); //3.小李修改商品价格+50 productLi.setPrice(productLi.getPrice()+50); productMapper.updateById(productLi); //4.小王修改商品价格-30 productWang.setPrice(productWang.getPrice()-30); int result = productMapper.updateById(productWang); if(result == 0){ //操作失败,重试 Product productNew = productMapper.selectById(1); productNew.setPrice(productNew.getPrice()-30); productMapper.updateById(productNew); } //5.老板查询商品价格 Product productBoss = productMapper.selectById(1); System.out.println("老板获取的商品价格为:" + productBoss.getPrice()); }
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
26
27
28