乐趣区

关于java:ShardingSphere分库分表schema名称导致NPE问题排查记录

前段时间把 ShardingSphere 降级到了 5.1.1 版本,奈何官网版本升级太快跟不上速度,这不最近又发现了一个 BUG。

问题景象

数据库做了分库分表,在须要查问多表数据进行 merge 的时候产生了一个 NPE 的异样。

Caused by: java.lang.NullPointerException
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByValue.getOrderValuesCaseSensitiveFromTables(OrderByValue.java:73) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByValue.getOrderValuesCaseSensitive(OrderByValue.java:64) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByValue.<init>(OrderByValue.java:58) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByStreamMergedResult.orderResultSetsToQueue(OrderByStreamMergedResult.java:56) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByStreamMergedResult.<init>(OrderByStreamMergedResult.java:50) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.ShardingDQLResultMerger.build(ShardingDQLResultMerger.java:89) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.ShardingDQLResultMerger.merge(ShardingDQLResultMerger.java:63) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.infra.merge.MergeEngine.executeMerge(MergeEngine.java:90) ~[shardingsphere-infra-merge-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.infra.merge.MergeEngine.merge(MergeEngine.java:80) ~[shardingsphere-infra-merge-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement.mergeQuery(ShardingSpherePreparedStatement.java:487) ~[shardingsphere-jdbc-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement.getResultSet(ShardingSpherePreparedStatement.java:435) ~[shardingsphere-jdbc-core-5.1.1.jar:5.1.1]
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getFirstResultSet(DefaultResultSetHandler.java:237) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:187) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109) ~[mybatis-3.5.3.jar:3.5.3]
    at jdk.internal.reflect.GeneratedMethodAccessor346.invoke(Unknown Source) ~[?:?]
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
    at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63) ~[mybatis-3.5.3.jar:3.5.3]
    at com.sun.proxy.$Proxy410.query(Unknown Source) ~[?:?]
    at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:108) ~[pagehelper-5.1.11.jar:?]
    at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) ~[mybatis-3.5.3.jar:3.5.3]
    at com.sun.proxy.$Proxy410.query(Unknown Source) ~[?:?]
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) ~[mybatis-3.5.3.jar:3.5.3]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427) ~[mybatis-spring-2.0.6.jar:2.0.6]
    ... 95 more

问题排查

跟踪到报错的中央,发现是这个中央的 schema 是个null,从而引发了 NPE。

一路往上看代码,最终定位到了这个获取 schema 的中央,也就是元数据去 getDefaultSchema 获取默认的 schema 名称改的时候拿到了一个空值。

进入这个办法后发现通过 schemaschemas这个 map 里获取名称的时候是个空值,debug到这个中央其实发现了问题。

咱们的 schemaName 配置的是 orderTrade 蕴含有大写字符的,所以 name 传进来的是 orderTrade,然而问题是这个schemas 确是ordertrade

所以很显然,这里获取不到正确的 schema 名称,导致了这个 NPE 的异样,那么问题是这个 schemas 是怎么加载进来的呢?

咱们发现 schemas 是在创立元数据的时候,通过构造函数赋值的,那么只有找到这个赋值的中央应该就能发现问题了。

通过一番查找,找到了调用的中央,这个 schemas 值就是 databaseMap 中的 value,那么咱们要持续看这个databaseMap 是如何初始化来的。

持续看源码,找到了 databaseMap 进行初始化的中央,原来是通过 DatabaseLoader 去加载元数据的时候初始化的,那么这个 load 办法是怎么解决的呢?

从代码来看他蕴含了两局部的信息,第一个是咱们本人通过 schema 配置的一些分库分表的配置信息,另外一部分则是数据库默认的一些表的元数据,比方 mysqlinformation_schema 这些,那咱们只有看本人配置的那局部就能够了,也就是 SchemaLoader.load(dataSourceMap, rules, props) 办法。

看他实际上就是获取数据库是什么类型,比方 mysql,而后去加载表的元数据,最初new 进去 ShardingSphereSchema,间接看最初的new 局部代码就行了。

进入这个办法,霎时就水落石出了,原来在 put 的时候对所有的 schemaName 进行了小写解决,所以在最下面咱们去 get 的时候必定会拿到一个空值,最终导致 merge 的时候产生了 NPE 异样。

解决方案

当初问题起因曾经发现了,那么该如何解决呢?总不能不让他人配置的时候不让写大写吧,本着能不能白嫖一个 PR 的想法,又去给 Sharding 提了一个 Issue。

就我点了根烟的功夫,回头就给我回复说新版本曾经修复了,心愿落空了,修复计划就是查问的时候也做小写解决了,好吧,那就这样吧。

退出移动版