当前位置: 首页 > news >正文

MyBatis-Plus 进阶功能:分页插件与乐观锁的实战指南

在实际开发中,分页查询和并发数据修改是常见需求。MyBatis-Plus 提供了开箱即用的分页插件和乐观锁机制,无需手动编写复杂 SQL 即可实现这些功能。本文将详细讲解这两个功能的原理、配置步骤和实战案例,帮助你快速在项目中落地。

一、分页插件:轻松实现分页查询

传统分页需要手动编写LIMIT语句并计算总页数,MyBatis-Plus 的分页插件通过拦截 SQL 自动添加分页条件,简化了这一过程。

1.1 分页插件的配置

使用分页插件只需两步:添加配置类并注册分页拦截器。

步骤 1:编写配置类
@Configuration
@MapperScan("com.qcby.mybatisplus.mapper") // 扫描Mapper接口所在包
public class MyBatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分页插件,指定数据库类型(MySQL)interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}

 

说明

  • MybatisPlusInterceptor是 MyBatis-Plus 的核心拦截器,用于整合各种插件;
  • PaginationInnerInterceptor是分页插件,需指定数据库类型(如DbType.MYSQL)以适配不同数据库的分页语法。

1.2 基础分页查询:使用 selectPage 方法

MyBatis-Plus 的BaseMapper提供了selectPage方法,直接传入Page对象即可实现分页。

测试代码
@Test
public void testBasicPage() {// 创建Page对象,参数1:当前页(从1开始),参数2:每页条数Page<User> page = new Page<>(1, 5);// 调用selectPage,第二个参数为查询条件(null表示无额外条件)userMapper.selectPage(page, null);// 获取分页数据List<User> records = page.getRecords(); // 当前页数据列表long current = page.getCurrent(); // 当前页long size = page.getSize(); // 每页条数long total = page.getTotal(); // 总记录数long pages = page.getPages(); // 总页数boolean hasPrevious = page.hasPrevious(); // 是否有上一页boolean hasNext = page.hasNext(); // 是否有下一页// 打印结果System.out.println("当前页数据:" + records);System.out.println("当前页:" + current);System.out.println("每页条数:" + size);System.out.println("总记录数:" + total);System.out.println("总页数:" + pages);System.out.println("是否有上一页:" + hasPrevious);System.out.println("是否有下一页:" + hasNext);
}

 

生成的 SQL
分页插件会自动在 SQL 后添加LIMIT条件,并查询总记录数:

-- 查询当前页数据
SELECT id, username, age, email, is_deleted 
FROM t_user 
WHERE is_deleted=0 
LIMIT 0,5-- 查询总记录数
SELECT COUNT(1) 
FROM t_user 
WHERE is_deleted=0

1.3 自定义 XML 分页:复杂查询的分页实现

对于复杂的自定义 SQL(如多表关联查询),可通过 XML 编写 SQL 并结合分页插件实现分页。

步骤 1:在 Mapper 接口定义方法
@Repository
public interface UserMapper extends BaseMapper<User> {/*** 根据年龄查询用户,分页返回* @param page 分页对象(必须放在参数第一位)* @param age 年龄条件* @return 分页结果*/IPage<User> selectPageByAge(@Param("page") Page<User> page, @Param("age") Integer age);
}

注意:分页对象Page必须作为第一个参数,MyBatis-Plus 会自动从Page中获取分页参数。

步骤 2:在 XML 中编写 SQL
<!-- resources/mapper/UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qcby.mybatisplus.mapper.UserMapper"><!-- 自定义分页查询:查询年龄大于指定值的用户 --><select id="selectPageByAge" resultType="com.qcby.mybatisplus.entity.User">SELECT id, username, age, email FROM t_user WHERE age > #{age}</select>
</mapper>

说明:无需手动添加LIMIT,分页插件会自动处理。

步骤 3:配置 XML 路径(可选)

如果 XML 文件不在默认路径(resources/mapper/),需在配置文件中指定:

mybatis-plus:mapper-locations: classpath:mybatis/mapper/*.xml # 自定义XML路径
步骤 4:测试自定义分页
@Test
public void testCustomPage() {// 创建分页对象(第1页,每页5条)Page<User> page = new Page<>(1, 5);// 调用自定义分页方法,查询年龄大于20的用户IPage<User> userPage = userMapper.selectPageByAge(page, 20);// 分页结果与基础分页一致,可通过IPage获取各种信息System.out.println("总记录数:" + userPage.getTotal());System.out.println("当前页数据:" + userPage.getRecords());
}

 

生成的 SQL

-- 查询当前页数据(自动添加LIMIT)
SELECT id, username, age, email 
FROM t_user 
WHERE age > 20 
LIMIT 0,5-- 查询总记录数
SELECT COUNT(1) 
FROM t_user 
WHERE age > 20

二、乐观锁:解决并发数据修改冲突

当多个用户同时修改同一条数据时,可能出现 "丢失更新" 问题(后提交的修改覆盖先提交的修改)。乐观锁通过版本号机制避免这一问题。

2.1 问题场景:并发修改导致的数据不一致

假设有一件商品初始价格为 100 元,小李和小王同时操作:

  • 小李计划涨价 50 元(目标价格 150 元);
  • 小王计划降价 30 元(目标价格 70 元)。

若无锁机制,最终价格可能变为 70 元(小王的修改覆盖了小李的),而非正确的 120 元(100+50-30)。

数据库增加商品表 

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)
);
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本 ', 100);

 

添加实体 

@Data
public class Product {private Long id;private String name;private Integer price;private Integer version;
}

添加mapper 

 
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
模拟冲突的代码
@Test
public void testConcurrentUpdate() {// 小李查询商品Product p1 = productMapper.selectById(1L);System.out.println("小李查询的价格:" + p1.getPrice()); // 100// 小王查询商品Product p2 = productMapper.selectById(1L);System.out.println("小王查询的价格:" + p2.getPrice()); // 100// 小李修改:100 + 50 = 150p1.setPrice(p1.getPrice() + 50);int result1 = productMapper.updateById(p1);System.out.println("小李修改结果:" + result1); // 1(成功)// 小王修改:100 - 30 = 70p2.setPrice(p2.getPrice() - 30);int result2 = productMapper.updateById(p2);System.out.println("小王修改结果:" + result2); // 1(成功)// 最终价格Product p3 = productMapper.selectById(1L);System.out.println("最终价格:" + p3.getPrice()); // 70(错误,应为120)
}

 

 

2.2 乐观锁的实现原理

乐观锁通过 "版本号" 机制实现,核心逻辑如下:

  1. 数据库表添加version字段(初始值 0);
  2. 查询数据时,获取当前version
  3. 更新数据时,条件中携带查询时的version,并将version自增 1;
  4. version不匹配(已被其他用户修改),则更新失败。

示例 SQL

  • 查询时获取版本:SELECT id, price, version FROM t_product WHERE id=1(假设版本为 0);
  • 小李更新:UPDATE t_product SET price=150, version=1 WHERE id=1 AND version=0(成功,版本变为 1);
  • 小王更新:UPDATE t_product SET price=70, version=1 WHERE id=1 AND version=0(失败,因版本已变为 1)。

2.3 乐观锁的实现步骤

步骤 1:数据库添加 version 字段
-- 商品表结构
CREATE TABLE t_product (id BIGINT PRIMARY KEY,name VARCHAR(30) COMMENT '商品名称',price INT COMMENT '价格',version INT DEFAULT 0 COMMENT '乐观锁版本号' -- 新增版本字段
);-- 初始化数据
INSERT INTO t_product (id, name, price) VALUES (1, '外星人笔记本', 100);
步骤 2:实体类添加 @Version 注解
@Data
public class Product {private Long id;private String name;private Integer price;@Version // 标识该字段为乐观锁版本号private Integer version;
}
步骤 3:配置乐观锁插件

在之前的配置类中添加乐观锁拦截器:

@Configuration
@MapperScan("com.qcby.mybatisplus.mapper")
public class MyBatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));// 乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}
}

2.4 测试乐观锁效果

重新运行并发修改测试:

@Test
public void testOptimisticLock() {// 小李查询商品(version=0)Product p1 = productMapper.selectById(1L);// 小王查询商品(version=0)Product p2 = productMapper.selectById(1L);// 小李修改:price=150,version=0→1p1.setPrice(p1.getPrice() + 50);int result1 = productMapper.updateById(p1); System.out.println("小李修改结果:" + result1); // 1(成功)// 小王修改:条件version=0,但实际已变为1,修改失败p2.setPrice(p2.getPrice() - 30);int result2 = productMapper.updateById(p2); System.out.println("小王修改结果:" + result2); // 0(失败)// 小王重试:重新查询最新数据(version=1)if (result2 == 0) {p2 = productMapper.selectById(1L); // 此时price=150,version=1p2.setPrice(p2.getPrice() - 30); // 150-30=120result2 = productMapper.updateById(p2); // 更新条件version=1→2System.out.println("小王重试结果:" + result2); // 1(成功)}// 最终价格Product p3 = productMapper.selectById(1L);System.out.println("最终价格:" + p3.getPrice()); // 120(正确)
}

 

生成的 SQL

  • 小李的更新:UPDATE t_product SET name=?, price=?, version=1 WHERE id=1 AND version=0(成功);
  • 小王首次更新:UPDATE t_product SET name=?, price=?, version=1 WHERE id=1 AND version=0(失败,version 不匹配);
  • 小王重试更新:UPDATE t_product SET name=?, price=?, version=2 WHERE id=1 AND version=1(成功)。

2.5 乐观锁的适用场景

  • 适合读多写少的场景(如商品详情页频繁查询,偶尔修改价格);
  • 不适合写冲突频繁的场景(此时悲观锁更合适);
  • 核心是 "乐观":认为冲突概率低,通过版本号检测冲突,而非提前加锁阻塞。

http://www.lryc.cn/news/600986.html

相关文章:

  • org.apache.lucene.search.Query#rewrite(IndexSearcher)过时讲解
  • 框架式3D打印机结构设计cad【9张】三维图+设计说明书
  • Windows Server存储池,虚拟磁盘在系统启动后不自动连接需要手动连接
  • vulhub Earth靶场攻略
  • Java:采用mybatis+pagehealper优雅的实现分页功能
  • 文件操作认识
  • connect系统调用及示例
  • 使用Python实现单词记忆软件
  • 零基础学习性能测试第三章:jmeter性能组件应用(事件,并发,定时器)
  • 大模型 vs 轻量模型:架构与使用场景对比
  • 单片机ADC机理层面详细分析(一)
  • nfls dp 刷题 题解
  • C++平衡二叉搜索树易错点
  • C++ 类型萃取:深入理解与实践
  • git推送文件失败
  • vulhub-earth靶机攻略
  • 显式等待和隐式等待的区别
  • 伟淼科技李志伟:破解二代接班传承困局,系统性方案破除三代魔咒
  • pytorch学习笔记-自定义卷积
  • Bert项目--新闻标题文本分类
  • C# 位运算及应用
  • 【简述】C++11/14/17/20/23 中的关键新特性
  • 无源域自适应综合研究【3】
  • ts-node 深入全面讲解
  • IntelliJ IDEA 的“缩短命令行”:解决长类路径的利器
  • 《Moco: Momentum Contrast for Unsupervised Visual Representation Learning》论文精读笔记
  • CentOS 7 安装 MySQL 8.4.6(二进制包)指南
  • 学习嵌入式的第三十一天-数据结构-(2025.7.23)网络协议封装
  • Houdini快速模拟烟雾
  • 从0开始学linux韦东山教程Linux驱动入门实验班(5)