一个SQL直接在数据库里执行是正常的,但是使用mybatis执行后得到的List的长度是正确的,但是里面所有的元素均为null.已排除resultType这个低级原因.
后来debug进去发现执行select * from这样的SQL语句会有此类问题,并且打印mybatis日志,显示如下信息
<== Columns:
<== Row:
<== Row:
这样看来是没有取到数据,甚至连metadata都未能获得,由于还有其他的报错信息,找了好久的原因
从mapper的方法开始debug进去,一步步进入发现问题的关键在于DefaultResultSetHandler.getRowValue返回的对象是null此时调用栈如下
getRowValue:396, DefaultResultSetHandler (org.apache.ibatis.executor.resultset) handleRowValuesForSimpleResultMap:354, DefaultResultSetHandler (org.apache.ibatis.executor.resultset) handleRowValues:328, DefaultResultSetHandler (org.apache.ibatis.executor.resultset) handleResultSet:301, DefaultResultSetHandler (org.apache.ibatis.executor.resultset) handleResultSets:194, DefaultResultSetHandler (org.apache.ibatis.executor.resultset) query:65, PreparedStatementHandler (org.apache.ibatis.executor.statement) query:79, RoutingStatementHandler (org.apache.ibatis.executor.statement) doQuery:63, SimpleExecutor (org.apache.ibatis.executor) queryFromDatabase:324, BaseExecutor (org.apache.ibatis.executor) query:156, BaseExecutor (org.apache.ibatis.executor) query:109, CachingExecutor (org.apache.ibatis.executor) query:83, CachingExecutor (org.apache.ibatis.executor) selectList:148, DefaultSqlSession (org.apache.ibatis.session.defaults) selectList:141, DefaultSqlSession (org.apache.ibatis.session.defaults) invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect) invoke:78, NativeMethodAccessorImpl (jdk.internal.reflect) invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect) invoke:567, Method (java.lang.reflect) invoke:433, SqlSessionTemplate$SqlSessionInterceptor (org.mybatis.spring) selectList:-1, $Proxy113 (jdk.proxy2) selectList:230, SqlSessionTemplate (org.mybatis.spring) executeForMany:168, MybatisMapperMethod (com.baomidou.mybatisplus.core.override) execute:82, MybatisMapperMethod (com.baomidou.mybatisplus.core.override) invoke:61, MybatisMapperProxy (com.baomidou.mybatisplus.core.override) select:-1, $Proxy114 (jdk.proxy2)
那么此时问题来到上面这个字段为何初始化时没有设置正确的值,由于有缓存的原因,重启程序,进入构造方法,在ProjectionsContextEngine.createProjectionsContext调用了该构造方法.而这个值的根源来自该方法的入参SelectStatement,此时调用栈如下
createProjectionsContext:69, ProjectionsContextEngine (org.apache.shardingsphere.sql.parser.binder.segment.select.projection.engine):99, SelectStatementContext (org.apache.shardingsphere.sql.parser.binder.statement.dml) getDMLStatementContext:103, SQLStatementContextFactory (org.apache.shardingsphere.sql.parser.binder) newInstance:87, SQLStatementContextFactory (org.apache.shardingsphere.sql.parser.binder) createRouteContext:99, DataNodeRouter (org.apache.shardingsphere.underlying.route) executeRoute:89, DataNodeRouter (org.apache.shardingsphere.underlying.route) route:76, DataNodeRouter (org.apache.shardingsphere.underlying.route) route:54, PreparedQueryPrepareEngine (org.apache.shardingsphere.underlying.pluggble.prepare) executeRoute:96, BasePrepareEngine (org.apache.shardingsphere.underlying.pluggble.prepare) prepare:83, BasePrepareEngine (org.apache.shardingsphere.underlying.pluggble.prepare) prepare:183, ShardingPreparedStatement (org.apache.shardingsphere.shardingjdbc.jdbc.core.statement) execute:143, ShardingPreparedStatement (org.apache.shardingsphere.shardingjdbc.jdbc.core.statement) invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect) invoke:78, NativeMethodAccessorImpl (jdk.internal.reflect) invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect) invoke:567, Method (java.lang.reflect) invoke:59, PreparedStatementLogger (org.apache.ibatis.logging.jdbc) execute:-1, $Proxy146 (jdk.proxy3) query:64, PreparedStatementHandler (org.apache.ibatis.executor.statement) query:79, RoutingStatementHandler (org.apache.ibatis.executor.statement) doQuery:63, SimpleExecutor (org.apache.ibatis.executor) queryFromDatabase:324, BaseExecutor (org.apache.ibatis.executor) query:156, BaseExecutor (org.apache.ibatis.executor) query:109, CachingExecutor (org.apache.ibatis.executor) query:83, CachingExecutor (org.apache.ibatis.executor) selectList:148, DefaultSqlSession (org.apache.ibatis.session.defaults) selectList:141, DefaultSqlSession (org.apache.ibatis.session.defaults) invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect) invoke:78, NativeMethodAccessorImpl (jdk.internal.reflect) invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect) invoke:567, Method (java.lang.reflect) invoke:433, SqlSessionTemplate$SqlSessionInterceptor (org.mybatis.spring) selectList:-1, $Proxy113 (jdk.proxy2) selectList:230, SqlSessionTemplate (org.mybatis.spring) executeForMany:168, MybatisMapperMethod (com.baomidou.mybatisplus.core.override) execute:82, MybatisMapperMethod (com.baomidou.mybatisplus.core.override) invoke:61, MybatisMapperProxy (com.baomidou.mybatisplus.core.override) select:-1, $Proxy114 (jdk.proxy2)
那么现在的问题就来到了为何foundValues的值是false,我们继续来到了DefaultResultSetHandler.applyPropertyMappings这个方法,该方法代码如下
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
按理说第二个if判断应该为true的,但是由于mappedColumnNames是一个空集合,导致没有进入
该方法代码如下
public List getMappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
if (mappedColumnNames == null) {
loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);
mappedColumnNames = mappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));
}
return mappedColumnNames;
}
这里使用了一个类似懒加载的方式,关键在于loadMappedAndUnmappedColumnNames方法,代码如下
private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
List mappedColumnNames = new ArrayList<>();
List unmappedColumnNames = new ArrayList<>();
final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
final Set mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
for (String columnName : columnNames) {
final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
if (mappedColumns.contains(upperColumnName)) {
mappedColumnNames.add(upperColumnName);
} else {
unmappedColumnNames.add(columnName);
}
}
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
然后会发现columnNames居然是个空集合,但是这个时候columnNames作为属性已经设置完
顺着调用栈向回看来到DataNodeRouter.createRouteContext这一方法,代码如下:
private RouteContext createRouteContext(final String sql, final List
问题已经比较明显了,查看sqlStatement内部数据结构就会发现没有column信息,那么这么看来是org.apache.shardingsphere.sql.parser.SQLParserEngine.parse方法出了问题.也许对于shardingsphere而言此处没有回去column信息是可以的,但是对于上面的mybatis就不行了.