Mybatis callSettersOnNulls 查询返回Map时设置值为null

Love The Way You Lie 2024-04-20 09:36 154阅读 0赞

这两天用到了Spring boot + Mybatis 做项目,使用了resultType=”HashMap” 接收Mybatis查询返回的数据。以列名作为key,值作为value。结果发现部分列没有返回相对应的k-v对,导致了程序发生了错误。

然后开始debug大法,这里开始调用invoke方法,使用MapperMethod 的execute方法。

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. try {
  3. if (Object.class.equals(method.getDeclaringClass())) {
  4. return method.invoke(this, args);
  5. } else if (isDefaultMethod(method)) {
  6. return invokeDefaultMethod(proxy, method, args);
  7. }
  8. } catch (Throwable t) {
  9. throw ExceptionUtil.unwrapThrowable(t);
  10. }
  11. final MapperMethod mapperMethod = cachedMapperMethod(method);
  12. return mapperMethod.execute(sqlSession, args);
  13. }  
  14. MapperMethod 通过判断sql的执行类型和返回值类型来选择执行方法,这里是select语句,并且返回值类型是list,就会选择executeForMany方法。
  15. public Object execute(SqlSession sqlSession, Object[] args) {
  16. Object result;
  17. switch (command.getType()) {
  18. .... 省略部分代码
  19. case SELECT:
  20. if (method.returnsVoid() && method.hasResultHandler()) {
  21. executeWithResultHandler(sqlSession, args);
  22. result = null;
  23. } else if (method.returnsMany()) {
  24. result = executeForMany(sqlSession, args);
  25. } else if (method.returnsMap()) {
  26. result = executeForMap(sqlSession, args);
  27. } else if (method.returnsCursor()) {
  28. result = executeForCursor(sqlSession, args);
  29. } else {
  30. Object param = method.convertArgsToSqlCommandParam(args);
  31. result = sqlSession.selectOne(command.getName(), param);
  32. }
  33. break;
  34. case FLUSH:
  35. result = sqlSession.flushStatements();
  36. break;
  37. default:
  38. throw new BindingException("Unknown execution method for: " + command.getName());
  39. }
  40. .... 省略部分代码
  41. return result;
  42. }

  然后执行selectList 方法,

  1. private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
  2. List<E> result;
  3. Object param = method.convertArgsToSqlCommandParam(args);
  4. if (method.hasRowBounds()) {
  5. RowBounds rowBounds = method.extractRowBounds(args);
  6. result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
  7. } else {
  8. result = sqlSession.<E>selectList(command.getName(), param);
  9. }
  10. // issue #510 Collections & arrays support
  11. if (!method.getReturnType().isAssignableFrom(result.getClass())) {
  12. if (method.getReturnType().isArray()) {
  13. return convertToArray(result);
  14. } else {
  15. return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
  16. }
  17. }
  18. return result;
  19. }

 再通过method 的invoke方法反射调用

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. SqlSession sqlSession = getSqlSession(
  3. SqlSessionTemplate.this.sqlSessionFactory,
  4. SqlSessionTemplate.this.executorType,
  5. SqlSessionTemplate.this.exceptionTranslator);
  6. try {
  7. Object result = method.invoke(sqlSession, args);
  8. 。。。。
  9. return result;
  10. }
  11. 。。。 省略
  12. }
  13. }

  然后执行select方法

  1. @Override
  2. public <E> List<E> selectList(String statement, Object parameter) {
  3. return this.selectList(statement, parameter, RowBounds.DEFAULT);
  4. }

  其实也就是调用了selectList方法,再执行Executor 的query方法。

  1. public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  2. try {
  3. MappedStatement ms = configuration.getMappedStatement(statement);
  4. return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  5. } catch (Exception e) {
  6. throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  7. } finally {
  8. ErrorContext.instance().reset();
  9. }
  10. }

  然后到了,主要看query方法

  1. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  2. BoundSql boundSql = ms.getBoundSql(parameter);
  3. CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  4. return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  5. }

  然后是

  1. @SuppressWarnings("unchecked")
  2. @Override
  3. public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  4. ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  5. 。。。
  6. List<E> list;
  7. try {
  8. queryStack++;
  9. list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
  10. if (list != null) {
  11. handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  12. } else {
  13. list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  14. }
  15. } finally {
  16. queryStack--;
  17. }
  18. 。。。
  19. return list;
  20. }

  来看看 queryFromDatabase方法,这里会加入缓存,然后执行query方法。

  1. private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  2. List<E> list;
  3. localCache.putObject(key, EXECUTION_PLACEHOLDER);
  4. try {
  5. list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  6. } finally {
  7. localCache.removeObject(key);
  8. }
  9. localCache.putObject(key, list);
  10. if (ms.getStatementType() == StatementType.CALLABLE) {
  11. localOutputParameterCache.putObject(key, parameter);
  12. }
  13. return list;
  14. }

  这里主要先获取了Mybatis的配置类,把拦截器放入到Handler类中,再生成具体的Statement,比如PreparedStatement等,然后执行Handler的query方法

  1. public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  2. Statement stmt = null;
  3. try {
  4. Configuration configuration = ms.getConfiguration();
  5. StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  6. stmt = prepareStatement(handler, ms.getStatementLog());
  7. return handler.<E>query(stmt, resultHandler);
  8. } finally {
  9. closeStatement(stmt);
  10. }
  11. }

  再反射调用query方法

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. try {
  3. Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  4. if (methods != null && methods.contains(method)) {
  5. return interceptor.intercept(new Invocation(target, method, args));
  6. }
  7. return method.invoke(target, args);
  8. } catch (Exception e) {
  9. throw ExceptionUtil.unwrapThrowable(e);
  10. }
  11. }

  就是这里

  1. @Override
  2. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  3. return delegate.<E>query(statement, resultHandler);
  4. }

  进去,这里的PreparedStatement开始执行execute方法,执行查询数据库,获取到具体的数据,然后对查询到的结果集进行处理。

  1. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  2. PreparedStatement ps = (PreparedStatement) statement;
  3. ps.execute();
  4. return resultSetHandler.<E> handleResultSets(ps);
  5. }  

看看handleResultSets方法,注释也说清楚了,处理返回的结果集。我们主要看看为什么没有返回数据是null的k-v对。

  1. //
  2. // HANDLE RESULT SETS
  3. //
  4. @Override
  5. public List<Object> handleResultSets(Statement stmt) throws SQLException {
  6. ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
  7. final List<Object> multipleResults = new ArrayList<Object>();
  8. int resultSetCount = 0;
  9. ResultSetWrapper rsw = getFirstResultSet(stmt);
  10. List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  11. int resultMapCount = resultMaps.size();
  12. validateResultMapsCount(rsw, resultMapCount);
  13. while (rsw != null && resultMapCount > resultSetCount) {
  14. ResultMap resultMap = resultMaps.get(resultSetCount);
  15. handleResultSet(rsw, resultMap, multipleResults, null);
  16. rsw = getNextResultSet(stmt);
  17. cleanUpAfterHandlingResultSet();
  18. resultSetCount++;
  19. }
  20. String[] resultSets = mappedStatement.getResultSets();
  21. if (resultSets != null) {
  22. while (rsw != null && resultSetCount < resultSets.length) {
  23. ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
  24. if (parentMapping != null) {
  25. String nestedResultMapId = parentMapping.getNestedResultMapId();
  26. ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
  27. handleResultSet(rsw, resultMap, null, parentMapping);
  28. }
  29. rsw = getNextResultSet(stmt);
  30. cleanUpAfterHandlingResultSet();
  31. resultSetCount++;
  32. }
  33. }
  34. return collapseSingleResultList(multipleResults);
  35. }
  36. DefaultResultSetHandler 类的handleResultSet方法,
  37. private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  38. try {
  39. if (parentMapping != null) {
  40. handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
  41. } else {
  42. if (resultHandler == null) {
  43. DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
  44. handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
  45. multipleResults.add(defaultResultHandler.getResultList());
  46. } else {
  47. handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
  48. }
  49. }
  50. } finally {
  51. // issue #228 (close resultsets)
  52. closeResultSet(rsw.getResultSet());
  53. }
  54. }

  然后看看handleRowValues方法

  1. //
  2. // HANDLE ROWS FOR SIMPLE RESULTMAP
  3. //
  4. public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  5. if (resultMap.hasNestedResultMaps()) {
  6. ensureNoRowBounds();
  7. checkResultHandler();
  8. handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  9. } else {
  10. handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  11. }
  12. }

  

  然后是handleRowValuesForSimpleResultMap

  1. private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
  2. throws SQLException {
  3. DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
  4. skipRows(rsw.getResultSet(), rowBounds);
  5. while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
  6. ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
  7. Object rowValue = getRowValue(rsw, discriminatedResultMap);
  8. storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
  9. }
  10. }

 接着是获取每一行的值

  1. //
  2. // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
  3. //
  4. private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
  5. final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  6. Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
  7. if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
  8. final MetaObject metaObject = configuration.newMetaObject(rowValue);
  9. boolean foundValues = this.useConstructorMappings;
  10. if (shouldApplyAutomaticMappings(resultMap, false)) {
  11. foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
  12. }
  13. foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
  14. foundValues = lazyLoader.size() > 0 || foundValues;
  15. rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
  16. }
  17. return rowValue;
  18. } 

再看看applyAutomaticMappings 方法,final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);

就是获取到每一行具体那一列的值,

  1. value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive
  2. 会判断value是否为null,当某一行某一列的值是null的时候,会callSettersOnNulls这个配置的值,是否把这一行这一列以列名为keynull为值返回。
  3. private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  4. List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  5. boolean foundValues = false;
  6. if (!autoMapping.isEmpty()) {
  7. for (UnMappedColumnAutoMapping mapping : autoMapping) {
  8. final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
  9. if (value != null) {
  10. foundValues = true;
  11. }
  12. if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
  13. // gcode issue #377, call setter on nulls (value is not 'found')
  14. metaObject.setValue(mapping.property, value);
  15. }
  16. }
  17. }
  18. return foundValues;
  19. }

  

 到了这里,也就知道了mybatis 中callSettersOnNulls 参数的真正作用是查询的某一行某一列为null,是否返回。

转载于:https://www.cnblogs.com/fillPv/p/11165796.html

发表评论

表情:
评论列表 (有 0 条评论,154人围观)

还没有评论,来说两句吧...

相关阅读