程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • mybatis-plus

    • mybatis-plus概述
    • MyBatis-Plus入门案例
    • BaseMapper 增删改查
    • Service层 增删改查
    • MyBatis-Plus常用注解
    • MyBatis-Plus条件构造器
    • MyBatis-Plus查询条件
    • MyBatis-Plus常用插件
      • 1. 分页插件:轻松实现高效分页
        • 1.1 配置分页插件
        • 1.2 分页查询基础用法
        • 1.3 分页插件高级特性
        • 1.3.1 排序功能
        • 1.3.2 只查询总记录数
        • 1.3.3 溢出总页数处理
      • 2. 自定义分页查询:满足复杂业务需求
        • 2.1 自定义Mapper方法
        • 2.2 编写XML映射文件
        • 2.3 调用自定义分页查询
        • 2.4 自定义分页的关键点
      • 3. 乐观锁插件:并发数据安全保障
        • 3.1 乐观锁的原理
        • 3.2 乐观锁的应用场景
        • 3.3 乐观锁插件的配置
        • 3.4 实体类中的版本字段配置
        • 3.5 乐观锁的实际应用示例
        • 3.5.1 准备数据库表
        • 3.5.2 模拟并发修改场景
        • 3.5.3 更新失败时的重试机制
        • 3.6 乐观锁的实现原理分析
        • 3.7 乐观锁使用注意事项
      • 4. 插件组合使用
        • 4.1 分页+条件查询+排序
        • 4.2 分页+乐观锁+自定义查询
      • 5. 常见问题与解决方案
        • 5.1 分页插件问题
        • 5.1.1 分页结果不准确
        • 5.1.2 多表关联查询分页问题
        • 5.2 乐观锁问题
        • 5.2.1 更新一直失败
        • 5.2.2 部分字段更新问题
    • 通用枚举和多数据源
    • MyBatisX 插件
  • MyBatis-plus
  • mybatis-plus
scholar
2024-01-21
目录

MyBatis-Plus常用插件

# MyBatis-Plus常用插件

# 1. 分页插件:轻松实现高效分页

MyBatis-Plus提供的分页插件使开发者能够轻松实现数据库分页查询,无需手写复杂的SQL语句。本章将详细讲解分页插件的配置、使用及优化技巧。

# 1.1 配置分页插件

要使用分页功能,首先需要在项目中配置分页插件。MyBatis-Plus 3.4.0及以上版本使用拦截器的方式配置插件。

/**
 * MyBatis-Plus插件配置类
 * 用于注册分页插件等MyBatis-Plus提供的功能增强插件
 */
@Configuration
@MapperScan("com.example.project.mapper") // 指定Mapper接口的扫描路径
public class MyBatisPlusConfig {

    /**
     * 配置MyBatis-Plus插件拦截器
     * 在此拦截器中可以添加多个内部插件,如分页插件、乐观锁插件等
     * @return 配置好的拦截器实例
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 创建MyBatis-Plus拦截器实例
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 添加分页插件,并指定数据库类型为MySQL
        // 支持的数据库类型包括:MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、PostgreSQL等
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        
        return interceptor;
    }
}
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

# 1.2 分页查询基础用法

分页查询的核心是Page<T>对象,它封装了分页的核心参数和方法。下面是分页查询的基本用法:

/**
 * 基础分页查询示例
 * 演示如何使用MyBatis-Plus分页插件进行简单的分页查询
 */
@Test
public void testBasicPage() {
    // 创建分页对象,指定当前页码和每页显示的记录数
    // 参数1:current - 当前页码,从1开始计数
    // 参数2:size - 每页显示的记录数
    Page<User> page = new Page<>(1, 5); 
    
    // 构建查询条件(可选)
    // 如不需要条件,可以传入null
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.ge(User::getAge, 18); // 查询年龄大于等于18的用户
    
    // 执行分页查询
    // selectPage方法会自动处理分页逻辑,无需手动编写LIMIT语句
    // 查询结果会被封装到page对象中
    Page<User> userPage = userMapper.selectPage(page, queryWrapper);
    
    // 获取分页信息
    System.out.println("总记录数: " + userPage.getTotal()); // 符合条件的总记录数
    System.out.println("当前页码: " + userPage.getCurrent()); // 当前页码
    System.out.println("每页记录数: " + userPage.getSize()); // 每页显示的记录数
    System.out.println("总页数: " + userPage.getPages()); // 总页数
    System.out.println("是否有上一页: " + userPage.hasPrevious()); // 是否有上一页
    System.out.println("是否有下一页: " + userPage.hasNext()); // 是否有下一页
    
    // 获取分页数据列表
    List<User> users = userPage.getRecords(); // 当前页的记录列表
    users.forEach(System.out::println);
}
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
29
30
31
32
33

# 1.3 分页插件高级特性

MyBatis-Plus的分页插件除了基本的分页功能外,还提供了许多高级特性。

# 1.3.1 排序功能

可以在创建Page对象时指定排序规则:

/**
 * 分页排序示例
 * 演示如何在分页查询中添加排序条件
 */
@Test
public void testPageWithSort() {
    // 方式一:通过Page对象设置排序
    // 创建分页对象并设置排序规则
    Page<User> page = new Page<>(1, 10);
    // 按年龄降序,再按ID升序排列
    page.addOrder(OrderItem.desc("age"));
    page.addOrder(OrderItem.asc("id"));
    
    // 执行分页查询
    Page<User> userPage1 = userMapper.selectPage(page, null);
    
    // 方式二:通过条件构造器设置排序
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.orderByDesc(User::getAge)
                .orderByAsc(User::getId);
    
    Page<User> userPage2 = userMapper.selectPage(new Page<>(1, 10), queryWrapper);
    
    // 获取查询结果
    userPage2.getRecords().forEach(System.out::println);
}
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

# 1.3.2 只查询总记录数

有时我们只需要知道符合条件的记录总数,而不需要查询具体数据:

/**
 * 只查询总记录数示例
 */
@Test
public void testCountOnly() {
    // 创建分页对象,设置只查询总记录数
    Page<User> page = new Page<>(1, 10);
    page.setSearchCount(true); // 设置查询总记录数(默认为true)
    page.setMaxLimit(100L); // 设置最大单页限制数量
    
    // 查询条件
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.like(User::getName, "张");
    
    // 执行查询
    userMapper.selectPage(page, queryWrapper);
    
    // 获取总记录数
    long total = page.getTotal();
    System.out.println("符合条件的记录总数: " + total);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 1.3.3 溢出总页数处理

当请求的页码大于最大页码时,可以设置返回首页或最后一页:

/**
 * 页码溢出处理示例
 */
@Test
public void testPageOverflow() {
    // 假设总共只有3页数据,但请求第10页
    
    // 创建分页对象,并设置页码溢出后返回首页
    Page<User> page1 = new Page<>(10, 10, true);
    // true表示查询总记录数
    
    // 设置页码溢出后的处理策略
    PaginationInnerInterceptor interceptor = new PaginationInnerInterceptor();
    // 溢出总页数后是否进行处理
    interceptor.setOverflow(true);
    // 溢出后返回首页
    interceptor.setMaxLimit(500L); // 单页最大条数限制
    
    // 执行查询(此时会返回第1页的数据,而不是空结果)
    Page<User> result = userMapper.selectPage(page1, null);
    System.out.println("当前页码: " + result.getCurrent());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2. 自定义分页查询:满足复杂业务需求

虽然MyBatis-Plus内置的分页功能已经很强大,但在复杂业务场景中,我们可能需要自定义分页查询来满足特定需求。

# 2.1 自定义Mapper方法

在Mapper接口中定义自定义分页方法,第一个参数必须是Page<T>:

/**
 * 用户Mapper接口
 */
public interface UserMapper extends BaseMapper<User> {
    /**
     * 自定义分页查询方法
     * @param page 分页对象,必须是第一个参数
     * @param age 年龄条件
     * @param name 姓名条件(模糊匹配)
     * @return 分页结果,包含查询到的记录和分页信息
     */
    Page<UserVO> selectUserWithRolePage(
        @Param("page") Page<UserVO> page, 
        @Param("age") Integer age, 
        @Param("name") String name
    );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2.2 编写XML映射文件

为自定义分页方法编写对应的XML语句:

<!-- UserMapper.xml -->
<mapper namespace="com.example.project.mapper.UserMapper">
    <!-- 自定义分页查询 -->
    <!-- 
        注意:使用MyBatis-Plus分页插件时,无需手动添加LIMIT语句
        分页插件会自动识别Page参数并添加分页语句
    -->
    <select id="selectUserWithRolePage" resultType="com.example.project.vo.UserVO">
        SELECT 
            u.id, u.username, u.age, u.email,
            r.role_name
        FROM t_user u
        LEFT JOIN t_user_role ur ON u.id = ur.user_id
        LEFT JOIN t_role r ON ur.role_id = r.id
        <where>
            <!-- 动态条件:如果提供了年龄参数,则添加年龄条件 -->
            <if test="age != null">
                AND u.age > #{age}
            </if>
            <!-- 动态条件:如果提供了姓名参数,则添加姓名模糊查询条件 -->
            <if test="name != null and name != ''">
                AND u.username LIKE CONCAT('%', #{name}, '%')
            </if>
        </where>
        ORDER BY u.id DESC
    </select>
</mapper>
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

# 2.3 调用自定义分页查询

在Service层或测试代码中调用自定义分页方法:

/**
 * 自定义分页查询示例
 * 演示如何使用自定义的分页查询方法
 */
@Test
public void testCustomPage() {
    // 创建分页对象
    Page<UserVO> page = new Page<>(1, 5);
    
    // 设置查询参数
    Integer minAge = 20;  // 最小年龄
    String nameKeyword = "张";  // 姓名关键字
    
    // 调用自定义分页方法
    Page<UserVO> result = userMapper.selectUserWithRolePage(page, minAge, nameKeyword);
    
    // 处理分页结果
    System.out.println("总记录数: " + result.getTotal());
    System.out.println("总页数: " + result.getPages());
    
    // 遍历查询结果
    List<UserVO> userVOList = result.getRecords();
    userVOList.forEach(userVO -> {
        System.out.println("用户ID: " + userVO.getId());
        System.out.println("用户名: " + userVO.getUsername());
        System.out.println("角色名: " + userVO.getRoleName());
        System.out.println("--------------------");
    });
}
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
29

# 2.4 自定义分页的关键点

  1. 第一个参数必须是Page对象:这是MyBatis-Plus识别分页的关键。
  2. 无需手动添加LIMIT语句:分页插件会自动处理。
  3. 可以结合动态SQL:实现条件查询和复杂关联。
  4. 返回值也应为Page对象:保持一致的分页操作体验。

# 3. 乐观锁插件:并发数据安全保障

乐观锁是一种并发控制方法,它假设多用户并发的事务在处理时不会彼此互相影响,因此不会加锁。只在提交操作时检查是否违反数据完整性。

# 3.1 乐观锁的原理

乐观锁的核心原理是:

  1. 取出记录时,获取当前版本号(version字段值)
  2. 更新时,带上这个版本号
  3. 执行更新语句时,WHERE条件中加入版本号的判断
  4. 如果数据已被其他线程修改,版本号不匹配,更新失败

# 3.2 乐观锁的应用场景

乐观锁适用于以下场景:

  • 读多写少:大部分时间都在读取数据,偶尔才会修改的场景
  • 并发冲突较少:操作冲突概率较低的场景
  • 不允许数据被错误覆盖:必须确保数据准确性的业务

以商品价格修改为例,展示乐观锁的实际应用:

# 3.3 乐观锁插件的配置

首先,需要在MyBatis-Plus配置中添加乐观锁插件:

/**
 * MyBatis-Plus插件配置类
 */
@Configuration
public class MyBatisPlusConfig {
    /**
     * 配置MyBatis-Plus插件
     * 注册分页插件和乐观锁插件
     */
    @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
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.4 实体类中的版本字段配置

在实体类中,需要使用@Version注解标记版本号字段:

/**
 * 商品实体类
 * 演示乐观锁的使用
 */
@Data
@TableName("t_product")
public class Product {
    /**
     * 商品ID
     */
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    /**
     * 商品名称
     */
    private String name;
    
    /**
     * 商品价格
     */
    private Integer price;
    
    /**
     * 版本号,用于乐观锁控制
     * 使用@Version注解标记该字段为乐观锁版本号字段
     * 每次更新操作时,该字段会自动+1
     */
    @Version
    private Integer version;
}
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
29
30
31

# 3.5 乐观锁的实际应用示例

下面通过一个完整的例子来说明乐观锁的工作原理:

# 3.5.1 准备数据库表

-- 创建商品表,包含版本号字段
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, version) VALUES (1, '外星人笔记本', 100, 0);
1
2
3
4
5
6
7
8
9
10
11

# 3.5.2 模拟并发修改场景

/**
 * 乐观锁并发修改测试
 * 模拟两个用户同时修改同一商品价格的场景
 */
@Test
public void testOptimisticLock() {
    // 场景:两个用户同时获取商品信息,然后分别进行修改
    
    // 1. 小李查询商品信息
    Product productLi = productMapper.selectById(1L);
    System.out.println("小李查询到的商品价格:" + productLi.getPrice());
    
    // 2. 小王也查询商品信息
    Product productWang = productMapper.selectById(1L);
    System.out.println("小王查询到的商品价格:" + productWang.getPrice());
    
    // 3. 小李将价格+50
    productLi.setPrice(productLi.getPrice() + 50);
    // 更新操作,此时version会自动+1
    int resultLi = productMapper.updateById(productLi);
    System.out.println("小李修改结果:" + (resultLi > 0 ? "成功" : "失败"));
    
    // 4. 小王将价格-30
    productWang.setPrice(productWang.getPrice() - 30);
    // 更新操作,但由于此时数据库中version已经被小李的操作更新,所以此次更新会失败
    int resultWang = productMapper.updateById(productWang);
    System.out.println("小王修改结果:" + (resultWang > 0 ? "成功" : "失败"));
    
    // 5. 老板查询最终的商品价格
    Product productFinal = productMapper.selectById(1L);
    System.out.println("最终的商品价格:" + productFinal.getPrice());
    System.out.println("当前的版本号:" + productFinal.getVersion());
}
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
29
30
31
32
33

# 3.5.3 更新失败时的重试机制

在实际应用中,当乐观锁导致更新失败时,通常需要实现重试机制:

/**
 * 乐观锁并发修改测试(含重试机制)
 */
@Test
public void testOptimisticLockWithRetry() {
    // 1. 小李查询商品信息
    Product productLi = productMapper.selectById(1L);
    System.out.println("小李查询到的商品价格:" + productLi.getPrice());
    
    // 2. 小王也查询商品信息
    Product productWang = productMapper.selectById(1L);
    System.out.println("小王查询到的商品价格:" + productWang.getPrice());
    
    // 3. 小李将价格+50
    productLi.setPrice(productLi.getPrice() + 50);
    int resultLi = productMapper.updateById(productLi);
    System.out.println("小李修改结果:" + (resultLi > 0 ? "成功" : "失败"));
    
    // 4. 小王将价格-30
    productWang.setPrice(productWang.getPrice() - 30);
    int resultWang = productMapper.updateById(productWang);
    
    // 5. 如果小王修改失败,实现重试逻辑
    if (resultWang == 0) {
        System.out.println("小王修改失败,开始重试...");
        
        // 重新查询商品信息(获取最新版本)
        Product productWangRetry = productMapper.selectById(1L);
        System.out.println("重试查询到的商品价格:" + productWangRetry.getPrice());
        
        // 在最新价格基础上减去30
        productWangRetry.setPrice(productWangRetry.getPrice() - 30);
        
        // 再次尝试更新
        int retryResult = productMapper.updateById(productWangRetry);
        System.out.println("小王重试修改结果:" + (retryResult > 0 ? "成功" : "失败"));
    }
    
    // 6. 老板查询最终的商品价格
    Product productFinal = productMapper.selectById(1L);
    System.out.println("最终的商品价格:" + productFinal.getPrice());
    System.out.println("当前的版本号:" + productFinal.getVersion());
}
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 3.6 乐观锁的实现原理分析

当我们使用乐观锁插件时,MyBatis-Plus会在执行更新操作时自动添加版本号条件:

-- 原始更新SQL(不带乐观锁)
UPDATE t_product SET name = ?, price = ? WHERE id = ?;

-- 添加乐观锁后的SQL
UPDATE t_product 
SET name = ?, price = ?, version = version + 1 
WHERE id = ? AND version = ?;
1
2
3
4
5
6
7

关键点:

  1. 更新时会检查当前版本号是否与查询时的版本号一致
  2. 如果一致,则更新成功并将版本号+1
  3. 如果不一致,则说明数据已被其他线程修改,更新失败(返回影响行数为0)

# 3.7 乐观锁使用注意事项

  1. 仅支持updateById和update(entity, wrapper)方法:其他更新方法不支持乐观锁。
  2. 必须携带版本号:更新操作必须带上版本号字段。
  3. 支持的数据类型:version字段支持int、long、date、timestamp等类型。
  4. 使用场景:适用于读多写少,并发冲突概率低的场景。
  5. 需要处理更新失败:业务代码中需要判断更新结果,必要时实现重试逻辑。

# 4. 插件组合使用

MyBatis-Plus的插件可以组合使用,实现更丰富的功能。以下是一些最佳实践:

# 4.1 分页+条件查询+排序

/**
 * 综合查询示例
 * 结合分页、条件查询和排序
 */
@Test
public void testCombinedQuery() {
    // 创建分页对象
    Page<User> page = new Page<>(1, 10);
    
    // 创建条件构造器
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    wrapper.like(User::getName, "张")
           .between(User::getAge, 20, 30)
           .orderByDesc(User::getCreateTime);
    
    // 执行查询
    Page<User> result = userMapper.selectPage(page, wrapper);
    
    // 处理结果
    result.getRecords().forEach(System.out::println);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 4.2 分页+乐观锁+自定义查询

/**
 * 自定义SQL分页查询,并使用乐观锁更新
 */
@Test
public void testCustomPageWithLock() {
    // 1. 执行自定义分页查询
    Page<ProductVO> page = new Page<>(1, 5);
    Page<ProductVO> result = productMapper.selectProductWithCategoryPage(page);
    
    // 2. 获取第一个商品进行修改
    if (!result.getRecords().isEmpty()) {
        ProductVO productVO = result.getRecords().get(0);
        
        // 3. 转换为实体对象并修改价格
        Product product = new Product();
        product.setId(productVO.getId());
        product.setPrice(productVO.getPrice() + 100);
        product.setVersion(productVO.getVersion()); // 设置版本号
        
        // 4. 使用乐观锁更新
        int updateResult = productMapper.updateById(product);
        System.out.println("更新结果: " + (updateResult > 0 ? "成功" : "失败"));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 5. 常见问题与解决方案

# 5.1 分页插件问题

# 5.1.1 分页结果不准确

问题描述:查询结果的总记录数不符合预期。

解决方案:

  1. 检查是否正确配置了分页插件
  2. 确认数据库类型是否正确
  3. 避免在分页方法前使用SELECT COUNT(*)
// 错误的做法
int count = userMapper.selectCount(wrapper);  // 不要单独查询总数
Page<User> page = userMapper.selectPage(new Page<>(1, 10), wrapper);

// 正确的做法
Page<User> page = userMapper.selectPage(new Page<>(1, 10), wrapper);
long count = page.getTotal();  // 直接从分页结果获取总数
1
2
3
4
5
6
7

# 5.1.2 多表关联查询分页问题

问题描述:关联多个表的查询结果分页不正确。

解决方案:使用自定义分页查询,确保SQL语句正确使用JOIN。

# 5.2 乐观锁问题

# 5.2.1 更新一直失败

问题描述:使用乐观锁时,更新操作总是失败。

解决方案:

  1. 确保查询和更新之间没有其他线程修改数据
  2. 检查实体类中的版本号字段是否正确设置了@Version注解
  3. 实现重试机制

# 5.2.2 部分字段更新问题

问题描述:只更新部分字段时,乐观锁不生效。

解决方案:使用updateById方法或确保update方法中的实体对象包含版本号字段。

// 正确的部分字段更新方式
LambdaUpdateWrapper<Product> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Product::getId, 1L)
             .set(Product::getPrice, 150);

// 创建带版本号的实体对象
Product product = new Product();
product.setVersion(currentVersion);  // 必须设置当前版本号

// 执行更新
productMapper.update(product, updateWrapper);
1
2
3
4
5
6
7
8
9
10
11
编辑此页 (opens new window)
上次更新: 2025/03/16, 20:49:53
MyBatis-Plus查询条件
通用枚举和多数据源

← MyBatis-Plus查询条件 通用枚举和多数据源→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式