最近在使用MyBatis自定義的TypeHandler在對數據作基礎加工,在對ResultMap使用自定義TypeHandler時是沒有問題的,能夠正常進入TypeHandler中處理數據,可是當結果集被定義爲ResultType時老是不進入自定義的TypeHandler,基於這個狀況,不得再也不次打開MyBatis的源碼一探究竟java
/** * 自定義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>
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入手開始排查問題。mybatis
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方法:app
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的差別就出如今這裏ide
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); 方法:ui
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);方法:this
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類:code
基礎實現類:BaseStatementHandler,它有三個實現類,分別對應CallableStatementHandler(存儲過程)、PreparedStatementHandler(對應於JDBC中prepareStatement執行的SQL)、SimpleStatementHandler(對應於JDBC中Statement執行的SQL)
路由StatementHandler:RoutingStatementHandler,它無業務性,只是提供了一種路由,來產生相應的BaseStatementHandler
問題排查完畢,是配置自定義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