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

MyBatis-Plus分页控件使用及使用过程发现的一个坑

最近维护一个旧项目的时候,出现了一个BUG,经排查后发现是Mybatis-plus分页控件使用的时候需要注意的一个问题,故在本地使用MybatisPlus模拟出现了一下这个问题。

首先,先说一下MyBatis-Plus的使用:

1)引入所需的包:

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.10.1</version>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-extension</artifactId><version>3.5.10.1</version> <!-- 确保版本号正确 -->
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-jsqlparser</artifactId><version>3.5.10.1</version> <!-- 确保版本号正确 -->
</dependency>

2)Mapper需要继承BaseMapper

@Mapper
public interface ClassifyMapper extends BaseMapper<Classify> {List<Classify> testMybatisPlus(Page<?> page, @Param("artist") String artist);
}

3)编写对应的Mapper文件(下面的左连接是为了模拟项目实际问题编写的,artist是music表的字段):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zguiz.musicplayer.mapper.ClassifyMapper"><!-- 通用查询映射结果 --><resultMap id="BaseResultMap" type="com.zguiz.musicplayer.bean.Classify"><id column="id" property="id" /><result column="name" property="name" /><result column="iconurl" property="iconurl" /></resultMap><select id="testMybatisPlus" resultMap="BaseResultMap">select * from musclassifyleft join relmusicclass on (musclassify.id = relmusicclass.classid)left join music on (relmusicclass.musicid = music.id)where artist = #{artist}</select>
</mapper>

4)让Spring容器托管MybatisPlusInterceptor:

@Configuration
@MapperScan("com.zguiz.musicplayer.mapper")
public class PageConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor());return interceptor;}
}

5)配置Mapper路径:

mybatis-plus:mapper-locations: classpath*:com/zguiz/musicplayer/mapper/*.xml

6)调用方法:

@Override
public void testMybatisPlus() {Page<Classify> page = new Page<>(1,10);classifyMapper.testMybatisPlus(page,"张敬轩");
}

调用后会发现出现了SQL异常:

### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column 'artist' in 'where clause'
### The error may exist in file [D:\WorkSpace\IdeaProject\MusicPlayer\target\classes\com\zguiz\musicplayer\mapper\ClassifyMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT COUNT(*) AS total FROM musclassify WHERE artist = ?
### Cause: java.sql.SQLSyntaxErrorException: Unknown column 'artist' in 'where clause'
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Unknown column 'artist' in 'where clause'] with root causejava.sql.SQLSyntaxErrorException: Unknown column 'artist' in 'where clause'at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120) ~[mysql-connector-java-8.0.20.jar:8.0.20]at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.20.jar:8.0.20]at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.20.jar:8.0.20]at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953) ~[mysql-connector-java-8.0.20.jar:8.0.20]at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:370) ~[mysql-connector-java-8.0.20.jar:8.0.20]at com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44) ~[HikariCP-3.4.5.jar:na]at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.execute(HikariProxyPreparedStatement.java) ~[HikariCP-3.4.5.jar:na]at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65) ~[mybatis-3.5.19.jar:3.5.19]

从异常信息可以推测是Mybatis-plus分页插件在对总行数统计的时候动态生产SQL时异常,将左联的表全部去除导致的。接下来分析PaginationInnerInterceptor源码的willDoQuery方法,这个方法是在执行查询前查询总行数的方法:

@Override
public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);if (page == null || page.getSize() < 0 || !page.searchCount() || resultHandler != Executor.NO_RESULT_HANDLER) {return true;}BoundSql countSql;MappedStatement countMs = buildCountMappedStatement(ms, page.countId());if (countMs != null) {countSql = countMs.getBoundSql(parameter);} else {countMs = buildAutoCountMappedStatement(ms);String countSqlStr = autoCountSql(page, boundSql.getSql());PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mpBoundSql.parameterMappings(), parameter);PluginUtils.setAdditionalParameter(countSql, mpBoundSql.additionalParameters());}CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql);List<Object> result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql);long total = 0;if (CollectionUtils.isNotEmpty(result)) {// 个别数据库 count 没数据不会返回 0Object o = result.get(0);if (o != null) {total = Long.parseLong(o.toString());}}page.setTotal(total);return continuePage(page);
}

其他代码不关注,我们重点关注下autoCountSql方法,这个方法是自动优化计算总行数SQL的方法:

/*** 获取自动优化的 countSql** @param page 参数* @param sql  sql* @return countSql*/
public String autoCountSql(IPage<?> page, String sql) {if (!page.optimizeCountSql()) {return lowLevelCountSql(sql);}try {Select select = (Select) JsqlParserGlobal.parse(sql);// https://github.com/baomidou/mybatis-plus/issues/3920  分页增加union语法支持if (select instanceof SetOperationList) {return lowLevelCountSql(sql);}PlainSelect plainSelect = (PlainSelect) select;………// 包含 join 连表,进行判断是否移除 join 连表if (optimizeJoin && page.optimizeJoinOfCountSql()) {List<Join> joins = plainSelect.getJoins();if (CollectionUtils.isNotEmpty(joins)) {boolean canRemoveJoin = true;String whereS = Optional.ofNullable(plainSelect.getWhere()).map(Expression::toString).orElse(StringPool.EMPTY);// 不区分大小写whereS = whereS.toLowerCase();for (Join join : joins) {if (!join.isLeft()) {canRemoveJoin = false;break;}FromItem rightItem = join.getRightItem();String str = "";if (rightItem instanceof Table) {Table table = (Table) rightItem;str = Optional.ofNullable(table.getAlias()).map(Alias::getName).orElse(table.getName()) + StringPool.DOT;} else if (rightItem instanceof ParenthesedSelect) {ParenthesedSelect subSelect = (ParenthesedSelect) rightItem;/* 如果 left join 是子查询,并且子查询里包含 ?(代表有入参) 或者 where 条件里包含使用 join 的表的字段作条件,就不移除 join */if (subSelect.toString().contains(StringPool.QUESTION_MARK)) {canRemoveJoin = false;break;}str = subSelect.getAlias().getName() + StringPool.DOT;}// 不区分大小写str = str.toLowerCase();if (whereS.contains(str)) {/* 如果 where 条件里包含使用 join 的表的字段作条件,就不移除 join */canRemoveJoin = false;break;}for (Expression expression : join.getOnExpressions()) {if (expression.toString().contains(StringPool.QUESTION_MARK)) {/* 如果 join 里包含 ?(代表有入参) 就不移除 join */canRemoveJoin = false;break;}}}if (canRemoveJoin) {plainSelect.setJoins(null);}}}// 优化 SQLplainSelect.setSelectItems(COUNT_SELECT_ITEM);return select.toString();} catch (JSQLParserException e) {// 无法优化使用原 SQLlogger.warn("optimize this sql to a count sql has exception, sql:\"" + sql + "\", exception:\n" + e.getCause());} catch (Exception e) {logger.warn("optimize this sql to a count sql has error, sql:\"" + sql + "\", exception:\n" + e);}return lowLevelCountSql(sql);
}

可以看到存在优化连接、排序操作。在判断左连接中,可以看到以下代码:

str是指表名(或者表别名),当where 存在表别名的时候将canRemoveJoin标志为false,也就是不去除链接,后续代码也能看到对canRemoveJoin的判断,如果为true则去除连接:

至此,问题的原因也能找到了,我们可选以下几种方式解决:

  1. 在where查询字段前加上表名(别名),即可避免被优化左连接:

<select id="testMybatisPlus" resultMap="BaseResultMap">select * from musclassifyleft join relmusicclass on (musclassify.id = relmusicclass.classid)left join music on (relmusicclass.musicid = music.id)where music.artist = #{artist}
</select>

2. 可以在构建Page对象的时候设置OptimizeCountSql为false,该字段是用于设置是否针对查询总行数进行优化

@Override
public void testMybatisPlus() {Page<Classify> page = new Page<>(1,10);page.setOptimizeCountSql(false);classifyMapper.testMybatisPlus(page,"张敬轩");
}

3.可以自己在Mapper中写一个计算总行数的SQL,并将id设置到countId中:

@Override
public void testMybatisPlus() {Page<Classify> page = new Page<>(1,10);page.setCountId("com.zguiz.musicplayer.mapper.ClassifyMapper.countClassify");classifyMapper.testMybatisPlus(page,"张敬轩");
}

4. 将CountId设置为ID+_mpCount,这个方法是Mybatis-Plus默认的查询总行数的方法,如果指定CountId,会避免进行总行数SQL优化如("com.zguiz.musicplayer.mapper.ClassifyMapper.testMybatisPlus_mpCount")。

相关文章:

MyBatis-Plus分页控件使用及使用过程发现的一个坑

最近维护一个旧项目的时候&#xff0c;出现了一个BUG&#xff0c;经排查后发现是Mybatis-plus分页控件使用的时候需要注意的一个问题&#xff0c;故在本地使用MybatisPlus模拟出现了一下这个问题。 首先&#xff0c;先说一下MyBatis-Plus的使用&#xff1a; 1&#xff09;引入…...

STM32的APB1和APB2的区别

STM32微控制器中的APB1和APB2的区别 STM32微控制器中的APB1和APB2是两种不同的外设总线&#xff0c;主要区别在于时钟速度、连接的外设以及用途。以下是它们的详细对比&#xff1a; 1. 时钟速度 APB1 (Advanced Peripheral Bus 1): 低速总线&#xff0c;时钟频率通常为系统时钟…...

JS一些小知识点

一、|| 运算符 plain this.ctx.body { type: type || 0, // ||在此处用法用于默认值填充&#xff0c;判断是否传参或该值是否存在&#xff0c;如果不存在就使用||后买你的值作为默认值 code: code || 0, msg: msg || SUCCESS, data: data || {}, ...others }; 二、trim() 方…...

手写Tomcat:实现基本功能

首先&#xff0c;Tomcat是一个软件&#xff0c;所有的项目都能在Tomcat上加载运行&#xff0c;Tomcat最核心的就是Servlet集合&#xff0c;本身就是HashMap。Tomcat需要支持Servlet&#xff0c;所以有servlet底层的资源&#xff1a;HttpServlet抽象类、HttpRequest和HttpRespon…...

C#变量与变量作用域详解

一、变量基础 1. ‌声明与初始化‌ 声明语法‌&#xff1a;<数据类型> <变量名>&#xff08;如 int age; string name&#xff09;‌初始化要求‌&#xff1a; 1、 类或结构体中的字段变量&#xff08;全局变量&#xff09;‌无需显式初始化‌&#xff0c;默认值…...

SV学习笔记——数组、队列

一、定宽数组 定宽数组是静态变量&#xff0c;编译时便已经确定其大小&#xff0c;其可以分为压缩定宽数组和非压缩定宽数组:压缩数组是定义在类型后面&#xff0c;名字前面;非压缩数组定义在名字后面。Bit [7:0][3:0] name; bit[7:0] name [3:0]; 1.1定宽数组声明 数组的声…...

API调试工具的无解困境:白名单、动态IP与平台设计问题

引言 你是否曾经在开发中遇到过这样的尴尬情形&#xff1a;你打开了平台的API调试工具&#xff0c;准备一番操作&#xff0c;结果却发现根本无法连接到平台&#xff1f;别急&#xff0c;问题出在调试工具本身。今天我们要吐槽的就是那些神奇的开放平台API调试工具&#xff0c;…...

Git清理本地残留的、但已经在服务器上被删除的分支

要筛选出已经被服务器删除的本地分支&#xff0c;并在本地删除这些分支&#xff0c;可以按照以下步骤进行操作&#xff1a; 步骤 1: 获取远程分支信息&#xff0c;确保本地的远程分支信息是最新的&#xff1a; git fetch -p步骤 2: 列出本地分支和远程分支&#xff1a; git …...

HTTPS实现内容加密的逻辑

加密过程 使用非对称加密&#xff0c;网站生成公钥和私钥浏览器获取到网站公钥&#xff08;通过验证和解析CA证书&#xff09;&#xff0c;随即生成一串字符串&#xff0c;然后使用公钥加密&#xff0c;发送给网站。网站用私钥将加密内容解析&#xff0c;然后使用这串字符串对…...

使用vue3.0+electron搭建桌面应用并打包exe

使用vue3.0electron搭建桌面应用并打包exe_如何使用electron将vue3vite开发完的项目打包成exe应用程序-CSDN博客...

JSAR 基础 1.2.1 基础概念_空间小程序

JSAR 基础 1.2.1 基础概念_空间小程序 空间空间自由度可嵌入空间空间小程序 最新的技术进展表明&#xff0c;官网之前的文档准备废除了&#xff0c;基于xsml的开发将退出历史舞台&#xff0c;three.js和普通web结合的技术将成为主导。所以后续学习请移步three.js学习路径&#…...

mysql练习

创建数据库db_ck&#xff0c;再创建表t_hero&#xff0c;将四大名著中的主要人物都插入这个表中&#xff0c;将实现过程中sql提交上上来 1、创建数据库db_ck mysql> create database db_ck; 2、创建表t_hero mysql> use db_ck Database changed mysql> create table …...

2025年2月平价旗舰手机性能对比

1、荣耀Magic7 点评&#xff1a;缺席潜望式长焦&#xff0c;3X直立长焦体验还行。兼顾性能、游戏、屏幕、影像、续航、快充等诸多方面&#xff0c;且外围配置比较齐全。 2、vivo x200 点评&#xff1a;潜望式长焦相机&#xff0c;拍照效果好&#xff0c;30W无线充电着实鸡肋&a…...

基于Spring Boot的扶贫助农系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

物联网中如何增加其可扩展性 协议 网络 设备 还包括软件层面上的

物联网(IoT)系统的可扩展性是指系统能够随着设备数量、数据流量和业务需求的增长而灵活扩展的能力。为了增加物联网的可扩展性,需要从协议、网络、设备和软件等多个层面进行优化和设计。以下是一些具体的策略和方法: 1. 协议层面的可扩展性 1.1 采用轻量级协议 轻量级协议…...

基于DeepSeek与搜索引擎构建智能搜索摘要工具

基于DeepSeek与搜索引擎构建智能搜索摘要工具 1. 项目概述 本项目通过整合DuckDuckGo搜索引擎与DeepSeek大语言模型,实现了一个智能搜索摘要生成工具。系统可自动执行以下流程: 输入查询语句进行全网搜索获取并解析搜索结果调用AI模型生成结构化摘要输出带来源标注的专业级…...

SQL 简介

SQL 简介 引言 结构化查询语言&#xff08;Structured Query Language&#xff0c;简称 SQL&#xff09;是一种用于数据库管理和操作的标准查询语言。它广泛应用于各个领域的数据库管理系统&#xff08;DBMS&#xff09;中&#xff0c;用于存储、检索和管理数据。SQL 不仅是数…...

BLUEM2引擎源码2025最新版

BLUE 引擎解析&#xff1a;传奇私服圈中的热门引擎 一、BLUE 引擎简介 BLUE 引擎是传奇私服圈子中较为知名的一款游戏引擎&#xff0c;它在传统的传奇引擎基础上进行了优化和扩展&#xff0c;使得私服开发者可以更加方便地搭建和管理服务器。相比于早期的 GEE、LEG、Hero 等引…...

pytest结合allure

Allure 一、文档二、指令三、装饰器3.1 allure.step装饰器3.2 allure.description装饰器3.3 allure.title装饰器3.4 allure.link、allure.issue 和 allure.testcase装饰器3.5 allure.epic、allure.feature 和 allure.story装饰器3.6 allure.severity装饰器 一、文档 allure文档…...

【渗透测试】基于时间的盲注(Time-Based Blind SQL Injection)

发生ERROR日志告警 查看系统日志如下&#xff1a; java.lang.IllegalArgumentException: Illegal character in query at index 203: https://api.weixin.qq.com/sns/jscode2session?access_token90_Vap5zo5UTJS4jbuvneMkyS1LHwHAgrofaX8bnIfW8EHXA71IRZwsqzJam9bo1m3zRcSrb…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)

笔记整理&#xff1a;刘治强&#xff0c;浙江大学硕士生&#xff0c;研究方向为知识图谱表示学习&#xff0c;大语言模型 论文链接&#xff1a;http://arxiv.org/abs/2407.16127 发表会议&#xff1a;ISWC 2024 1. 动机 传统的知识图谱补全&#xff08;KGC&#xff09;模型通过…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...