這篇文章主要看下mybatis是如何進行結果映射的。
以前提到過,addMapper的過程當中,將結果映射的配置保存在了ResultMap中。下面來看下這個類的屬性吧。java
private Configuration configuration;//全局配置 private String id;//惟一標識 private Class<?> type;//實體類 private List<ResultMapping> resultMappings;//嵌套查詢的相關配置 private List<ResultMapping> idResultMappings; private List<ResultMapping> propertyResultMappings; private Set<String> mappedColumns;//嵌套查詢的參數字段 private Set<String> mappedProperties;//嵌套查詢的屬性名 private boolean hasNestedResultMaps;//是否含有嵌套的ResultMap private boolean hasNestedQueries;//是否含有嵌套的子查詢
本篇文章,仍是基於那個最簡單的查詢來分析。所以,講不到嵌套查詢。所以ResultMap中就主要用到id,type這兩個屬性。如下是mapper.ProductMapper的一個抽象方法:mybatis
@Select("select * from product where id = #{id}") Product detail(long id);
這個方法對應的ResultMap的id爲"mapper.ProductMapper.detail-long"。type爲Product.class。app
ResultMap的構建是在addMapper的過程當中。代碼入口在MapperAnnotationBuilder的parseResultMap方法中。ide
private String parseResultMap(Method method) { Class<?> returnType = getReturnType(method);//經過反射獲取方法返回類型 ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);//獲取ConstructorArgs註解 Results results = method.getAnnotation(Results.class);//獲取Results註解,嵌套查詢主要是分析這個註解 TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class); String resultMapId = generateResultMapName(method);//拼接resultMap的id applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator);//構建ResultMap return resultMapId; }
能夠看到這個過程主要是經過反射來獲取的各個字段。咱們上面的例子比較簡單,沒有註解,因此,只有id和type字段有值。正如前面講的addMapper以後,這個ResultMap便存在了configuration中的resultMaps了。ui
繼續上一篇文章,上一篇文章,講到查詢除告終果,剩下DefaultResultSetHandler將結果按照配置映射好。一下爲DefaultResultSetHandler的handleResultSet方法。this
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) {//沒有指定resultHandler則使用默認的 DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);//映射數據 multipleResults.add(defaultResultHandler.getResultList());//拼接結果 } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } }
rsw是將jdbc的返回結果包裹了一層,方便處理。resultMap即是這個查詢對應的ResultMap,multipleResults則存放最終結果。parentMapping只有在嵌套查詢時纔會有值。
能夠看到咱們的案例parentMapping爲null,resultHandle也爲null。 能夠看到映射數據走的是handleRowValues方法。
而handleRowValues中又判斷了是否含有嵌套查詢,當沒有嵌套查詢時執行handleRowValuesForSimpleResultMap方法。code
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();//保留上下文 skipRows(rsw.getResultSet(), rowBounds);//邏輯分頁,去掉多餘行 while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {//遍歷數據行 ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);//處理Discriminate註解,本文不考慮 Object rowValue = getRowValue(rsw, discriminatedResultMap);//生成映射對象 storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());//將映射對象保存在resultHandler中。 } }
生成映射對象分紅了兩步,第一步構建映射對象,第二步是爲字段賦值。構建映射對象主要用的objectFactory,這個對象保存在configuration中。默認類是DefaultObjectFactory。對象
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { try { Constructor<T> constructor; if (constructorArgTypes == null || constructorArgs == null) {//當沒有傳入構造器參數時 constructor = type.getDeclaredConstructor();//獲取無參構造器 if (!constructor.isAccessible()) {//保證無參構造器可以訪問 constructor.setAccessible(true); } return constructor.newInstance();//構建對象並返回 } constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));//獲取參數類型對應的構造器 if (!constructor.isAccessible()) { constructor.setAccessible(true); } return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));//傳入參數構建對象並返回 } catch (Exception e) {//錯誤處理,省略 } }
DefaultObjectFactory類主要就是經過這個方法構造出對象的。代碼就主要是經過反射。
如今生成告終果對象了,但每一個字段都是空的,getRowValue中會調用applyAutomaticMappingst方法將查詢結果賦值到各個字段中。遞歸
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);//將每一個字段值和對應的屬性生成UnMappedColumnAutoMapping對象,並存到list中。 boolean foundValues = false; if (!autoMapping.isEmpty()) { for (UnMappedColumnAutoMapping mapping : autoMapping) {//遍歷每一個UnMappedColumnAutoMapping final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);//獲取值 if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { // gcode issue #377, call setter on nulls (value is not 'found') metaObject.setValue(mapping.property, value);//將對應的屬性填充上值 } } } return foundValues; }
createAutomaticMappings的過程比較繁瑣,主要就是遍歷結果,將jdbc的字段和對象的屬性名對應起來。就不具體看了。 最後看看 storeObject是如何將結果保存在resultHandler中的吧。storeObject本質是調用的ResultHandler的handleResult方法。接下來看看DefaultResultHandler類吧。ip
public class DefaultResultHandler implements ResultHandler<Object> { private final List<Object> list;//保存最終結果 public DefaultResultHandler() { list = new ArrayList<Object>(); } @SuppressWarnings("unchecked") public DefaultResultHandler(ObjectFactory objectFactory) { list = objectFactory.create(List.class); } @Override public void handleResult(ResultContext<? extends Object> context) { list.add(context.getResultObject());//將上下文中正在處理的對象添加到list中 } public List<Object> getResultList() { return list; } }
能夠看到默認的ResultHandler很是簡單。就是一個list,用add方法將每一個結果添加進來。每一個結果是經過context.getResultObject()獲得的。接下來看下DefaultResultContext類。
public class DefaultResultContext<T> implements ResultContext<T> { private T resultObject;//最後一個保存的數據 private int resultCount;//保存的結果個數 private boolean stopped;//是否中止 public DefaultResultContext() { resultObject = null; resultCount = 0; stopped = false; } @Override public T getResultObject() { return resultObject; } @Override public int getResultCount() { return resultCount; } @Override public boolean isStopped() { return stopped; } public void nextResultObject(T resultObject) { resultCount++; this.resultObject = resultObject; } @Override public void stop() { this.stopped = true; } }
能夠看到DefaultResultContext很是簡單,只是保存了下單個數據,以及處理過的結果個數。
只考慮這種簡單查詢的話,生成映射結果的流程並不複雜,最核心的兩步就是經過反射生成結果對象、填充各個字段的值。
若考慮嵌套查詢的話,能夠看到ResultMap中保存了子查詢的信息在resultMappings等字段中,能夠在裏面取出子查詢的配置,而後執行子查詢的方法,子查詢的執行過程其實和父查詢都是用以前分析的execute.query方法。有一點相似於遞歸。
若考慮延遲加載的話,mybatis的延遲加載主要使用:Javassist,Cglib實現