mybatis搭配shardingsphere返回数据里全是null的问题

问题描述:

一个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)

ProjectionsContext.projections

那么此时问题来到上面这个字段为何初始化时没有设置正确的值,由于有缓存的原因,重启程序,进入构造方法,在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

那么现在的问题就来到了为何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是一个空集合,导致没有进入

ResultSetWrapper.getMappedColumnNames

该方法代码如下

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 parameters, final boolean useCache) {
        SQLStatement sqlStatement = parserEngine.parse(sql, useCache);
        try {
            SQLStatementContext sqlStatementContext = SQLStatementContextFactory.newInstance(metaData.getSchema(), sql, parameters, sqlStatement);
            return new RouteContext(sqlStatementContext, parameters, new RouteResult());
            // TODO should pass parameters for master-slave
        } catch (final IndexOutOfBoundsException ex) {
            return new RouteContext(new CommonSQLStatementContext(sqlStatement), parameters, new RouteResult());
        }
    } 
  

问题已经比较明显了,查看sqlStatement内部数据结构就会发现没有column信息,那么这么看来是org.apache.shardingsphere.sql.parser.SQLParserEngine.parse方法出了问题.也许对于shardingsphere而言此处没有回去column信息是可以的,但是对于上面的mybatis就不行了.

你可能感兴趣的:(util,java,mybatis,shardingsphere,mybatis-plus)