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<String> {
@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配置文件:
<select id="getByApplicationIds" parameterType="list" resultType="censorModel">
<include refid="getAll"/>
where application_id in
<foreach collection="list" item="applicationId" index="index"
open="(" close=")" separator=",">
#{applicationId}
</foreach>
</select>
mybatis-config.xml中对于typeHandler的配置
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="java.lang.String"
handler="com.meituan.fd.crm.common.typehandler.SecurityStringVarcharTypeHandler"/>
</typeHandlers>
3、问题排查
3.1、MyBatis执行查询等操作的基础类为:DefaultSqlSession,其代码为:
public <T> T selectOne(String statement) {
return this.<T>selectOne(statement, null);
}
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>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 <K, V> Map<K, V> selectMap(String statement, String mapKey) {
return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
}
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
}
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
final List<?> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory());
final DefaultResultContext context = new DefaultResultContext();
for (Object o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
Map<K, V> selectedMap = mapResultHandler.getMappedResults();
return selectedMap;
}
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
List<E> 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 <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//获取本次执行SQL的相关配置信息
//MappedStatement中包含了一次执行的所以配置信息,包括SQL、配置参数等等
MappedStatement ms = configuration.getMappedStatement(statement);
//由executor调度执行查询,executor主要有两类:BaseExecutor(基础)、CachingExecutor(执行二级缓存)
//现有系统这边没有开启二级缓存,因此我这边进入BaseExecutor
List<E> 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 <E> List<E> 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 <E> List<E> 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<E> list;
try {
queryStack++;
//resultHandler不为空的情况下由缓存获取,此处resultHandler为空
list = resultHandler == null ? (List<E>) 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 <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> 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 <E> List<E> 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.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//JDBC级别的SQL执行
ps.execute();
//resultSetHandler只有一个实现类:DefaultResultSetHandler,进入看一下:
return resultSetHandler.<E> handleResultSets(ps);
}
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//最终执行完成后获取的结果数据
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
//ResultSet包装类,内部包含了ResultSet,及其元数据
ResultSetWrapper rsw = getFirstResultSet(stmt);
//获取结果集配置
List<ResultMap> 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<Object> 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<String> 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<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
if (columnHandlers == null) {
//没有则新建一个当前列对应的TypeHandler Map
columnHandlers = new HashMap<Class<?>, 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 <T> TypeHandler<T> getTypeHandler(Class<T> type) {
return getTypeHandler((Type) type, null);
}
继续进入:
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
//获取当前列对应的JavaType所有已配置的TypeHandler
Map<JdbcType, TypeHandler<?>> 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<T> returned = (TypeHandler<T>) handler;
return returned;
}
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
//获取所有配置的ResultMapping
final List<ResultMapping> 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,因此使用如下配置方式即可解决问题:
<typeHandlers>
<typeHandler javaType="java.lang.String"
handler="com.meituan.fd.crm.common.typehandler.SecurityStringVarcharTypeHandler"/>
<typeHandler jdbcType="VARCHAR" javaType="java.lang.String"
handler="com.meituan.fd.crm.common.typehandler.SecurityStringVarcharTypeHandler"/>
</typeHandlers>
此时,javaType为String,JdbcType为VARCHAR和null的都被配置为自定义的TypeHandler
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。