版本冲突导致分表情况下的分页查询异常问题
前提须知
- 首先明确一点,如果子查询中有分表的查询,同时没有将分表依据的字段作为查询参数,会导致无法进行分表查询处理,即抛出异常
- mybatisplus 分页查询流程,先查数据总数,再查分页数据
问题描述
- 当不传分表字段作为参数查询时,查询报错,报错信息如下
Must have sharding column with subquery.
意思就是查询参数缺少分表依据的字段。
2. 传分表字段查询,可以正常分页查询。
3. 但是,对于不传分表字段的情况,正常查询过程是扫描全部分表进行查询,然后汇总。
4. 因此,不传分表字段应该是没有问题的。
分析问题
- 当查询参数有传分表依据的字段数据,是能正常分页查询的,因此分表查询没有问题。
- 检查分页查询的sql,因为使用的是mybatis-plus,不用写mapper.xml
因此,需要将sql语句进行打印。
寻找问题
- 开启查询语句的debug日志级别,
添加启动参数
-Dlogging.level.com.*.*.*Mapper=DEBUG
- 发现sql语句问题
SELECT COUNT(1) FROM ( SELECT * FROM t_active_user_award WHERE state IN (?,?,?,?) ORDER BY create_time DESC ) TOTAL
- 在计算total时,生成的语句是含有子查询的,正常情况sql应该是不含子查询的
- debug 寻找 查询总数的sql再哪组装。
- PaginationInterceptor类
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler)PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
this.sqlParser(metaObject);
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
if (SqlCommandType.SELECT == mappedStatement.getSqlCommandType() && StatementType.CALLABLE != mappedStatement.getStatementType()) {
BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql");
Object paramObj = boundSql.getParameterObject();
IPage<?> page = (IPage)ParameterUtils.findPage(paramObj).orElse((Object)null);
if (null != page && page.getSize() >= 0L) {
if (this.limit > 0L && this.limit <= page.getSize()) {
this.handlerLimit(page);
}
String originalSql = boundSql.getSql();
Connection connection = (Connection)invocation.getArgs()[0];
if (page.isSearchCount() && !page.isHitCount()) {
// 这里进行 sql 组装
SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), this.countSqlParser, originalSql, metaObject);
// 此处进行total查询
this.queryTotal(sqlInfo.getSql(), mappedStatement, boundSql, page, connection);
if (!this.continueLimit(page)) {
return null;
}
}
...省略
return invocation.proceed();
} else {
return invocation.proceed();
}
} else {
return invocation.proceed();
}
}
进行 sql 组装
SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), this.countSqlParser, originalSql, metaObject);
组装过程
public class JsqlParserCountOptimize implements ISqlParser {
...省略
@Override
public SqlInfo parser(MetaObject metaObject, String sql) {
if (logger.isDebugEnabled()) {
logger.debug("JsqlParserCountOptimize sql=" + sql);
}
SqlInfo sqlInfo = SqlInfo.newInstance();
try {
Select selectStatement = (Select) CCJSqlParserUtil.parse(sql);
PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();
Distinct distinct = plainSelect.getDistinct();
// 1. 由于PlainSelect所属 jsqlpaarser.jar jar包版本过低,没有getGroupBy此方法,此处抛出异常
GroupByElement groupBy = plainSelect.getGroupBy();
List<OrderByElement> orderBy = plainSelect.getOrderByElements();
...省略
// 2.优化 SQL,正常的select count(*) from table where ....
plainSelect.setSelectItems(COUNT_SELECT_ITEM);
return sqlInfo.setSql(selectStatement.toString());
} catch (Throwable e) {
// e = net.sf.jsqlparser.statement.select.PlainSelect.getGroupBy()Lnet/sf/jsqlparser/statement/select/GroupByElement;
// 无法优化使用原 SQL
// 3. 返回的是有子查询的sql select count(*) from (select * from table where)
return sqlInfo.setSql(SqlParserUtils.getOriginalCountSql(sql));
}
}
}
异常
java.lang.NoSuchMethodError: net.sf.jsqlparser.statement.select.PlainSelect.getGroupBy()Lnet/sf/jsqlparser/statement/select/GroupByElement;
低版本jsqlpaarser-0.9.5.jar中,没有plainSelect.getGroupBy()方法,导致抛出异常,无法优化原SQL。
最后组装的sql结果为
SELECT COUNT(1) FROM ( SELECT * FROM table WHERE state IN (?,?,?,?) ORDER BY create_time DESC ) TOTAL
而想要的查询总数的语句
SELECT COUNT(1) FROM table WHERE state IN (?,?,?,?)
那么分页查询异常的原因就是如下
因为当子查询是分表查询语句时,不传分表依据字段,就会抛出异常
寻找jsqlpaarser包依赖问题,
版本冲突选择了低版本的包
处理问题:排除低版本的包。
那么maven依赖包冲突如何自动解决的呢
maven解决版本冲突原则
maven解决版本冲突主要是两个原则。
-
路径最短原则
-
优先声明原则:当出现路径长短相同的时候,谁先声明就用谁。
经过排查,发现本次问题导致采用了优先声明原则,因此,以后对于基础包等重要的包需要提前声明,而依赖的业务客户端包得放在最后。
到此问题就正常解决了,此次问题最终原因还是依赖问题。