sqlhelper集成dynamic多数据源的分页问题(非教学向)
一.问题描述
最近接手(顶锅)了公司的框架维护工作,第一项任务就是集成dynamic多数据源框架。(dynamic官方使用文档,本文不是教学,有兴趣的小伙伴可以自己查阅文档)。集成dynamic之后,一切都很顺利,但是测试到SQLHelper框架的分页功能,出错了:SQLHelper分页功能,全部是按照dynamic指定的primary数据源来处理分页sql的。比如我配置了mysql和oracle两个数据源,并且指定mysql为primary主数据源,然后不管使用哪个数据源进行查询,SQLHelper都是按照primary指定的主数据源mysql进行分页处理,导致用oracle数据源时分页语句sql报错。
二.解决思路
1.找到分页sql的处理入口
通过断点调试,找到了SQLHelper的com.jn.sqlhelper.dialect.internal.Dialect类和处理语句有关(实际上Dialect是一个接口,抽象子类AbstractDialect的属性LimitHandler才是实际sql的逻辑处理类,processSql是处理方法入口。为了方便理解,就以Dialect概括,一个Dialect对应一种数据源的处理)
2.Dialect和databaseId有关
databaseId其实就是数据源类型,Dialect是处理sql的类,每一种数据源对应一个Dialect,通过databaseId获取对应的Dialect然后进行sql加工处理,com.jn.sqlhelper.mybatis.plugins.pagination的PaginationHandler类beginIfSupportsLimit方法:
3.实际出问题的地方
其实就是databaseId的获取,com.jn.sqlhelper.mybatis包下MybatisUtils类getDatabaseId方法:
红框才是罪魁祸首(暂且先不看蓝框),通过断点调试,发现获取的是org.apache.ibatis.session.Configuration类的databaseId。那问题来了,Configuration类的databaseId是什么时候设置的呢?通过Configuration的setDatabaseId方法的断点调试,发现该方法是项目启动的时候才调用。org.mybatis.spring.SqlSessionFactoryBean类的buildSqlSessionFactory方法
这也解释通了为什么SQLHelper只按照某种数据源来处理分页语句。
4.为什么按照primary指定的主数据源处理?
一切从简,上关键代码(接Configuration的setDatabaseId方法):
com.baomidou.dynamic.datasource.DynamicRoutingDataSource类的determineDataSource方法
com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder类的peek()方法返回当前线程/代码使用的数据源名称,启动时没有触发push操作,所以返回为空。
空值则返回primary指定的默认数据库,所以Configuration的databaseId是dynamic在配置文件用primary指定的主数据源:
5.DynamicDataSourceContextHolder的push:
dynamic会将当前线程使用的数据源存入threadlocal里
com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor.invoke方法,会将当前线程使用的数据源名称存入
这个push触发的机制,应该是dynamic注解扫描到的地方。这也解释了为什么启动的时候peek返回空,因为没有触发push操作。
三.解决方案:
既然知道问题所在,那么就有解决思路了:MybatisUtils的getDatabaseId方法,返回正确的数据就行。
1.不走全局获取databaseId
如果能提前知道现在的数据源是什么,然后通过这个数据源获取databaseId就好了。巧的是DynamicDataSourceContextHolder类的peek()是一个静态方法,并且入参MappedStatement ms可以获取到dynamic配置的所有数据源信息:
2.通过connection获取databaseId
其实就是二.3的蓝框放前面就行,由于集成了连接池,所以不需要担心查询之前多连接一次数据库。tx.getConnection()源码有兴趣的也可以翻一下哦。
3.后续版本优化
四.小结
- SQLHelper根据databaseId(数据源类型)处理分页语句的类是LimitHandler;databaseId和LimitHandler、Dialect,是1:1:1的关系。
- springboot+tx.mybatis+dynamic+SQLHelper的技术选型下,SQLHelper在dynamic情况下分页处理有问题。
- 处理方案经实践,mybatis是完全可以适配的。不知道其他orm框架是否适用!
五.彩蛋时间
最近终于稍微脱离了CRDU的业务代码,开始往底层摸索了。框架需要适配dynamic,其实我也是什么都不懂,没办法只能查资料看看dynamic是什么,然后通过断点调试的方式探索开源框架的处理流程,说实话,最后发现问题并且解决问题的时候,还是有一点自豪感和成就感的!
框架这个版本的需求功能,需要用到的技术栈其实我基本没听过,但是有压力就有动力,能让自己多学一些总是好的!
另外附上和SQLHelper作者在github上的讨论地址:此次论剑,略输一筹(滑稽,HSPCode就是本萌新)