博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MyBatis ResultType使用自定义TypeHandler
阅读量:6252 次
发布时间:2019-06-22

本文共 19468 字,大约阅读时间需要 64 分钟。

1、背景

最近在使用MyBatis自定义的TypeHandler在对数据做基础加工,在对ResultMap使用自定义TypeHandler时是没有问题的,可以正常进入TypeHandler中处理数据,但是当结果集被定义为ResultType时总是不进入自定义的TypeHandler,基于这个情况,不得不再次打开MyBatis的源码一探究竟

2、基础代码

/** * 自定义TypeHandler * * @author fulibao * @version 1.0 * @created 2017/7/10 下午4:29 **/public class SecurityStringVarcharTypeHandler extends BaseTypeHandler
{ @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { //自定义代码 return rs.getString(columnName); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { //自定义代码 return rs.getString(columnIndex); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { //自定义代码 return cs.getString(columnIndex); }}

MyBatis XML配置文件:

mybatis-config.xml中对于typeHandler的配置

3、问题排查

3.1、MyBatis执行查询等操作的基础类为:DefaultSqlSession,其代码为:

public 
T selectOne(String statement) { return this.
selectOne(statement, null);}public
T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List
list = this.
selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; }}public
Map
selectMap(String statement, String mapKey) { return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);}public
Map
selectMap(String statement, Object parameter, String mapKey) { return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);}public
Map
selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { final List
list = selectList(statement, parameter, rowBounds); final DefaultMapResultHandler
mapResultHandler = new DefaultMapResultHandler
(mapKey, configuration.getObjectFactory(), configuration.getObjectWrapperFactory()); final DefaultResultContext context = new DefaultResultContext(); for (Object o : list) { context.nextResultObject(o); mapResultHandler.handleResult(context); } Map
selectedMap = mapResultHandler.getMappedResults(); return selectedMap;}public
List
selectList(String statement) { return this.selectList(statement, null);}public
List
selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT);}public
List
selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); List
result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}

可以看到无论是selectOne、selectMap、selectList最终调用的都是selectList方法,因此我们由selectList入手开始排查问题。

public 
List
selectList(String statement, Object parameter, RowBounds rowBounds) { try { //获取本次执行SQL的相关配置信息 //MappedStatement中包含了一次执行的所以配置信息,包括SQL、配置参数等等 MappedStatement ms = configuration.getMappedStatement(statement); //由executor调度执行查询,executor主要有两类:BaseExecutor(基础)、CachingExecutor(执行二级缓存) //现有系统这边没有开启二级缓存,因此我这边进入BaseExecutor List
result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); }}

BaseExecutor.query方法:

public 
List
query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //获取执行的BoundSql(详细内容可以参见另外一篇文章:通过BoundSql获取全部执行SQL--MyBatis源码分析) BoundSql boundSql = ms.getBoundSql(parameter); //创建缓存key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //执行真正的查询,见下面方法 return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}
public 
List
query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) throw new ExecutorException("Executor was closed.”); //缓存相关,查看是否需要清理缓存 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List
list; try { queryStack++; //resultHandler不为空的情况下由缓存获取,此处resultHandler为空 list = resultHandler == null ? (List
) localCache.getObject(key) : null; if (list != null) { //如果由缓存中取出了数据,那么处理存储过程相关的输出参数 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //由数据库获取数据及处理数据,方法见下面 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } deferredLoads.clear(); // issue #601 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); // issue #482 } } return list;}
private 
List
queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List
list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //真正的执行查询的方法,这个方法在BaseExecutor中是一个抽象方法,交由子类来完成,这里我们进入子类:SimpleExecutor中查看 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list;}
public 
List
doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { //获取MyBatis总的配置信息 Configuration configuration = ms.getConfiguration(); //创建StatementHandler,它用于执行SQL及处理执行结果 //它有四个实现类:见下图分析: StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //将参数等信息注入到执行SQL中 stmt = prepareStatement(handler, ms.getStatementLog()); //执行查询,一般的SQL执行都是PreparedStatementHandler,进入它的query中 return handler.
query(stmt, resultHandler); } finally { closeStatement(stmt); }}
public 
List
query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; //JDBC级别的SQL执行 ps.execute(); //resultSetHandler只有一个实现类:DefaultResultSetHandler,进入看一下: return resultSetHandler.
handleResultSets(ps);}
public List handleResultSets(Statement stmt) throws SQLException {  ErrorContext.instance().activity("handling results").object(mappedStatement.getId());  //最终执行完成后获取的结果数据  final List multipleResults = new ArrayList();  int resultSetCount = 0;  //ResultSet包装类,内部包含了ResultSet,及其元数据  ResultSetWrapper rsw = getFirstResultSet(stmt);  //获取结果集配置  List
resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); //处理结果集,MyBatis的查询结果就出自这里,进入看一下。。 handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResulSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults);}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException {  try {    if (parentMapping != null) {      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);    } else {      if (resultHandler == null) {        //默认的结果集处理Handler        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);        //处理没一行数据,并将数据存入defaultResultHandler        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);        //将数据存入multipleResults        multipleResults.add(defaultResultHandler.getResultList());      } else {        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);      }    }  } finally {    closeResultSet(rsw.getResultSet()); // issue #228 (close resultsets)  }}
private void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {  if (resultMap.hasNestedResultMaps()) {    ensureNoRowBounds();    checkResultHandler();    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);  } else {    //这里是最终处理数据的位置:    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);  }}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping)    throws SQLException {  //默认结果集存放处,记录了当前最新的一个结果和记录数  DefaultResultContext resultContext = new DefaultResultContext();  //根据RowBounds跳过不需要处理的数据  skipRows(rsw.getResultSet(), rowBounds);  //逐行处理数据  while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);    //处理一行数据,方法见下面    Object rowValue = getRowValue(rsw, discriminatedResultMap);    //存储处理后的一行数据    storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());  }}

//ResultType和ResultMap的差异就出现在这里

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {  final ResultLoaderMap lazyLoader = new ResultLoaderMap();  //获取查询实体的一个实例  Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null);  if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {    //MyBatis强大的MetaObject    final MetaObject metaObject = configuration.newMetaObject(resultObject);    boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;    if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {        //对于未被配置进入的列进行处理(ResultType所对应的列,一律不会被MyBatis配置,都会在这个方法中处理),方法见下面:            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;    }    //对于配置进入的列进行处理(ResultMap中的列都会被配置,会在此处处理),方法见下面    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;    foundValues = lazyLoader.size() > 0 || foundValues;    resultObject = foundValues ? resultObject : null;    return resultObject;  }  return resultObject;}
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {  final List
unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; //对每一列进行处理 for (String columnName : unmappedColumnNames) { //获取列名 String propertyName = columnName; if (columnPrefix != null && columnPrefix.length() > 0) { // When columnPrefix is specified, // ignore columns without the prefix. if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { propertyName = columnName.substring(columnPrefix.length()); } else { continue; } } //获取对应于实体的属性 final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase()); if (property != null && metaObject.hasSetter(property)) { //获取当前列对应的JavaType final Class
propertyType = metaObject.getSetterType(property); if (typeHandlerRegistry.hasTypeHandler(propertyType)) { //获取typeHandler,终于找到了问题的根节点了,在这儿就可以看出,ResultType中的列在MyBatis中也是使用TypeHandler处理的, //未进入自定义的TypeHandler的原因只能是MyBatis的查询TypeHandler的方法并没有查询到我们自定义的TypeHandler,我们看一下这个查询方法,见下面 final TypeHandler
typeHandler = rsw.getTypeHandler(propertyType, columnName); //由TypeHandler获取当前列执行结果 final Object value = typeHandler.getResult(rsw.getResultSet(), columnName); if (value != null || configuration.isCallSettersOnNulls()) { // issue #377, call setter on nulls if (value != null || !propertyType.isPrimitive()) { //由metaObject将列值设置进入属性值 metaObject.setValue(property, value); } foundValues = true; } } } } return foundValues;}

rsw.getTypeHandler(propertyType, columnName); 方法:

public TypeHandler
getTypeHandler(Class
propertyType, String columnName) { TypeHandler
handler = null; //获取当前列是否已查询过TypeHandler Map
, TypeHandler
> columnHandlers = typeHandlerMap.get(columnName); if (columnHandlers == null) { //没有则新建一个当前列对应的TypeHandler Map columnHandlers = new HashMap
, TypeHandler
>(); typeHandlerMap.put(columnName, columnHandlers); } else { //存在,则直接取出TypeHandler handler = columnHandlers.get(propertyType); } if (handler == null) { //由MyBatis配置文件中取出当前JavaType对应的TypeHandler //看到问题了吧,查询时,只传入了JavaType,并没有传JdbcType,是否意味着,会查询出JavaType为当前列的JavaType,JdbcType为null的TypeHandler?进入方法看一下就知道了 handler = typeHandlerRegistry.getTypeHandler(propertyType); // Replicate logic of UnknownTypeHandler#resolveTypeHandler // See issue #59 comment 10 if (handler == null || handler instanceof UnknownTypeHandler) { final int index = columnNames.indexOf(columnName); final JdbcType jdbcType = jdbcTypes.get(index); final Class
javaType = resolveClass(classNames.get(index)); if (javaType != null && jdbcType != null) { handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType); } else if (javaType != null) { handler = typeHandlerRegistry.getTypeHandler(javaType); } else if (jdbcType != null) { handler = typeHandlerRegistry.getTypeHandler(jdbcType); } } if (handler == null || handler instanceof UnknownTypeHandler) { handler = new ObjectTypeHandler(); } columnHandlers.put(propertyType, handler); } return handler;}

typeHandlerRegistry.getTypeHandler(propertyType); 方法:

public 
TypeHandler
getTypeHandler(Class
type) { return getTypeHandler((Type) type, null);}继续进入:private
TypeHandler
getTypeHandler(Type type, JdbcType jdbcType) { //获取当前列对应的JavaType所有已配置的TypeHandler Map
> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type); TypeHandler
handler = null; if (jdbcHandlerMap != null) { //获取jdbcType对应的TypeHandler //当前jdbcType为Null,取出的果然是:JavaType为当前列的JavaType,JdbcType为null的TypeHandler //问题揭开了,还是我们配置为有问题,我们自定义的TypeHandler配置的JavaType为:java.lang.String,JdbcType为VARCHAR,所以肯定不会使用我们自定义的TypeHandler handler = jdbcHandlerMap.get(jdbcType); if (handler == null) { handler = jdbcHandlerMap.get(null); } } if (handler == null && type != null && type instanceof Class && Enum.class.isAssignableFrom((Class
) type)) { handler = new EnumTypeHandler((Class
) type); } @SuppressWarnings("unchecked") // type drives generics here TypeHandler
returned = (TypeHandler
) handler; return returned;}
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; //获取所有配置的ResultMapping final List
propertyMappings = resultMap.getPropertyResultMappings(); //根据每个ResultMapping来处理每一列的值,ResultMapping中包含了当前列的TypeHandler for (ResultMapping propertyMapping : propertyMappings) { final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); if (propertyMapping.isCompositeResult() || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) || propertyMapping.getResultSet() != null) { //处理当前列的值,此方法见下面: Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); final String property = propertyMapping.getProperty(); // issue #541 make property optional if (value != NO_VALUE && property != null && (value != null || configuration.isCallSettersOnNulls())) { // issue #377, call setter on nulls if (value != null || !metaObject.getSetterType(property).isPrimitive()) { metaObject.setValue(property, value); } foundValues = true; } } } return foundValues;}

getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);方法:

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)    throws SQLException {  if (propertyMapping.getNestedQueryId() != null) {    return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);  } else if (propertyMapping.getResultSet() != null) {    addPendingChildRelation(rs, metaResultObject, propertyMapping);    return NO_VALUE;  } else if (propertyMapping.getNestedResultMapId() != null) {    // the user added a column attribute to a nested result map, ignore it    return NO_VALUE;  } else {    //由TypeHandler获取数据    final TypeHandler
typeHandler = propertyMapping.getTypeHandler(); final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); return typeHandler.getResult(rs, column); }}

StatementHandler类:

  • 基础实现类:BaseStatementHandler,它有三个实现类,分别对应CallableStatementHandler(存储过程)、PreparedStatementHandler(对应于JDBC中prepareStatement执行的SQL)、SimpleStatementHandler(对应于JDBC中Statement执行的SQL)

  • 路由StatementHandler:RoutingStatementHandler,它无业务性,只是提供了一种路由,来产生相应的BaseStatementHandler

4、解决方法

问题排查完毕,是配置自定义TypeHandler时指定了JaveType和JdbcType导致MyBatis找不到自定义的TypeHandler,因此使用如下配置方式即可解决问题:

此时,javaType为String,JdbcType为VARCHAR和null的都被配置为自定义的TypeHandler

转载地址:http://uufsa.baihongyu.com/

你可能感兴趣的文章
Android -- 自定义View小Demo,绘制钟表时间(一)
查看>>
Download Free Oracle Reports Building Guide eBook
查看>>
固定标题列、标题头table
查看>>
Geeks - Check whether a given graph is Bipartite or not 二分图检查
查看>>
使用Ant构建简单项目
查看>>
求两个有序数组的中位数(4. Median of Two Sorted Arrays)
查看>>
git锁和钩子以及图形化界面
查看>>
DataSnap Server 客户端调用 异常
查看>>
cesium之地图贴地量算工具效果篇
查看>>
C# winform DevExpress上传图片到数据库【转】
查看>>
指针和引用
查看>>
Review Board
查看>>
winform 程序中 调用wpf 窗体
查看>>
Chapter 24. Dynamic language support
查看>>
信息检索Reading List
查看>>
Advanced Customization of the jQuery Mobile Buttons | Appcropolis
查看>>
ubuntu配置bridge网桥
查看>>
批量修改sharepoint 2013站点里区域设置
查看>>
在尝试重新安装一个服务时遇到这样的错误:指定服务已标记为删除
查看>>
我的Android开发相关文章
查看>>