动态数据源下分页查询的疑难剖析与化解之道
大家好,我是欧阳方超,微信公众号同名。
1 概述
在项目开发过程中,我们采用了动态数据源的架构设计。然而,在进行分页查询操作时,却遭遇了一个棘手的问题。无论所调用的接口底层在查询时是 MySQL 数据库还是 PostgreSQL 数据库,最终打印出来的 SQL 语句一律呈现为 MySQL 的分页查询形式,也就是被转换成了 “limit m,n” 的格式。这一现象不仅令人困惑,还导致了在 PostgreSQL 数据库上执行分页查询时出现语法错误。本文将深入探究这一问题背后的原因,并详细阐述相应的整改方案,希望能为遇到类似问题的开发者提供有益的参考和借鉴。
2 问题出现
先看下配置类中Mybatis-plus拦截器的写法:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
在上述代码中,将分页拦截器的数据库类型明确设置为了 MySQL。在分页查询时,如果相应的SQL执行在postgresql上,会报下面的错误:
### SQL: select cck.cei_company_id, cck.cei_keyperson_name, cck.cei_position, ccs.cei_shareholder_proportion from db_cei.cei_company_keyperson cck left join db_cei.cei_company_shareholder ccs on cck.cei_company_id = ccs.cei_company_id and cck.cei_keyperson_name = ccs.cei_shareholder_name where cck.cei_use_flag = 0 and ccs.cei_use_flag = 0 and cck.cei_company_id = ? LIMIT ?,?
### Cause: org.postgresql.util.PSQLException: 错误: 不支持 LIMIT #,# 语法
建议:LIMIT和OFFSET子句要分隔开
位置:447
; bad SQL grammar []; nested exception is org.postgresql.util.PSQLException: 错误: 不支持 LIMIT #,# 语法
建议:LIMIT和OFFSET子句要分隔开
位置:447
因为当数据库类型设置为MySQL时,框架会把页面、每页数量这两个参数转换后最终拼接到SQL的limit后面,而拼接的形式为:limit m, n,而这种语法在postgresql中是不适用的,因此语法报错。
看来在多数据源的系统中把分页插件写死的做法是行不通的,那只能根据当前数据源类型动态创建拦截器。
3 初次尝试动态创建拦截器
于是,对代码进行了如下修改:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor() {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
String dbType = DataSourceContextHolder.getDbType();
// 根据当前数据源设置对应的数据库类型
if (DBTypeEnum.mysqldb.getValue().equals(dbType)) {
this.setDbType(DbType.MYSQL);
} else if (isPostgreSQL(dbType)) {
this.setDbType(DbType.POSTGRE_SQL);
}
super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
private boolean isPostgreSQL(String dbType) {
return DBTypeEnum.calcdb.getValue().equals(dbType)
|| DBTypeEnum.calcextdb.getValue().equals(dbType)
|| DBTypeEnum.ceiinfodb.getValue().equals(dbType)
|| DBTypeEnum.ceireportdb.getValue().equals(dbType)
|| DBTypeEnum.newshesudb.getValue().equals(dbType);
}
});
return interceptor;
}
上面修改完后,启动程序,如果你执行一下运行在MySQL上的分页查询,再执行运行在Postgresql上的分页查询,你会发现继续还会报错,但是假如程序启动后,先执行运行在PostgreSQL的查询,你会发现可以正常执行,接着执行运行在MySQL上的分页查询,也正常执行,这是因为分页插件会设置为首次调用接口时所对应的数据库类型,似乎存在某种隐藏的状态或机制在影响着分页拦截器的行为。
4 最终解决方案
经过深入研究和反复试验,找到了一种能够实现随意切换数据源进行分页查询且不会出错的解决方案,最后改成下面:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 创建MySQL分页拦截器
PaginationInnerInterceptor mysqlInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 创建PostgreSQL分页拦截器
PaginationInnerInterceptor postgresInterceptor = new PaginationInnerInterceptor(DbType.POSTGRE_SQL);
// 添加自定义分页拦截器,在运行时选择具体使用哪个分页拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor() {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
String dbType = DataSourceContextHolder.getDbType();
if (DBTypeEnum.mysqldb.getValue().equals(dbType)) {
mysqlInterceptor.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
} else if (isPostgreSQL(dbType)) {
postgresInterceptor.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
}
private boolean isPostgreSQL(String dbType) {
return DBTypeEnum.calcdb.getValue().equals(dbType)
|| DBTypeEnum.calcextdb.getValue().equals(dbType)
|| DBTypeEnum.ceiinfodb.getValue().equals(dbType)
|| DBTypeEnum.ceireportdb.getValue().equals(dbType)
|| DBTypeEnum.newshesudb.getValue().equals(dbType);
}
});
return interceptor;
}
在这个最终方案中,不再是在运行时动态修改单个分页拦截器的数据库类型,而是创建了针对 MySQL 和 PostgreSQL 的两个独立的分页拦截器。在自定义的分页拦截器的beforeQuery方法中,根据当前数据源类型,直接调用对应的分页拦截器的beforeQuery方法。这样一来,就能够确保在不同数据源的分页查询之间实现无缝切换,避免了之前出现的各种奇怪问题。
5 总结
通过这次在动态数据源分页查询过程中遇到的问题及解决过程,深刻认识到在处理多数据源和分页插件结合时,需要充分考虑到不同数据库的语法差异以及框架内部的运行机制,才能确保系统的稳定性和正确性。
我是欧阳方超,把事情做好了自然就有兴趣了,如果你喜欢我的文章,欢迎点赞、转发、评论加关注。我们下次见。