mybatis查詢語句的背后之封裝數據


 轉載請注明出處。。。

一、前言

繼上一篇mybatis查詢語句的背后,這一篇主要圍繞着mybatis查詢的后期操作,即跟數據庫交互的時候。由於本人也是一邊學習源碼一邊記錄,內容難免有錯誤或不足之處,還望諸位指正,本文只可當參考作用。謹記! 

二、分析

繼上一篇博文的查詢例子,mybatis在最后的查詢最終會走SimpleExecutor類的doQuery方法,

 1  @Override
 2   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 3     Statement stmt = null;
 4     try {
 5       Configuration configuration = ms.getConfiguration();
 6       // 這里也就是采用了策略模式(個人感覺有點像),實際的statementHandler為routingStatementHandler
 7       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 8       stmt = prepareStatement(handler, ms.getStatementLog());
 9       // 雖然是執行的routingStatementHandler.query,但返回結果的還是PreparedStatementHandler處理
10       return handler.query(stmt, resultHandler);
11     } finally {
12       closeStatement(stmt);
13     }
14   }
15 
16 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
17     Statement stmt;
18    // 使用了代理模式,也可以理解為對connection進行了一層包裝,這里的作用就是加了log處理
19     Connection connection = getConnection(statementLog);
20    //進行預編譯,即類似jdbc的 sql,如 select * from user where id=?
21     stmt = handler.prepare(connection, transaction.getTimeout());
22     // 對執行查詢的sql進行參數設置
23     handler.parameterize(stmt);
24     return stmt;
25   }

關於 handler.prepare的作用這里簡單介紹下,不做代碼分析。

會設置fetchSize,作用就是一次性從數據庫抓取數據,好像默認值是10條,如果每次只抓取一條,則進行rs.next的時候,會再次查庫。

如果是insert操作,並且數據庫主鍵自增且還設置了可以返回主鍵,則會還做獲取主鍵的操作。

先從設置參數說起,也就是handler.parameterize。先看下源碼,具體位置在DefaultParameterHandler類里面

 1 @Override
 2   public void setParameters(PreparedStatement ps) {
 3     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
 4     // 獲取配置文件里面的sql參數信息,如sql為select * from user where id=#{userId,jdbcType=INTEGER}
 5     // ParameterMapping 記錄了參數名也就是userId,還有記錄了對應的jdbc類型,還有對應的javaType等等,具體可以debug看下
 6     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
 7     if (parameterMappings != null) {
 8       for (int i = 0; i < parameterMappings.size(); i++) {
 9         ParameterMapping parameterMapping = parameterMappings.get(i);
10         if (parameterMapping.getMode() != ParameterMode.OUT) {
11           Object value;
12           String propertyName = parameterMapping.getProperty();
13           // 如果為true,那么sql參數中有類似 user.name 格式
14           if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
15             value = boundSql.getAdditionalParameter(propertyName);
16           } else if (parameterObject == null) {
17             value = null;
18           } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
19             value = parameterObject;
20           } else {
21             // metaObject 類似一個工具類,它里面有一個反射工廠,可以專門解析一個類的信息,如字段的setter/getter/屬性信息,這里不做多余介紹
22             // 1、下面詳細介紹 
23             MetaObject metaObject = configuration.newMetaObject(parameterObject);
24             value = metaObject.getValue(propertyName);// 取值
25           }
26           // 獲取對應的typeHandler,一般情況不設置的話,基本都是ObjectTypeHandler
27           TypeHandler typeHandler = parameterMapping.getTypeHandler();
28           JdbcType jdbcType = parameterMapping.getJdbcType();
29           if (value == null && jdbcType == null) {
30             jdbcType = configuration.getJdbcTypeForNull();
31           }
32           try {
33             // 進行設值
34             typeHandler.setParameter(ps, i + 1, value, jdbcType);
35           } catch (TypeException e) {
36             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
37           } catch (SQLException e) {
38             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
39           }
40         }
41       }
42     }
43   }

 對於上述代碼中的一部分這里負責將parameterObject的里面的值整出來(也就是傳入的參數),如果參數是map結構,就從map里面取值,如果不是,如單個非javabean參數,則直接取值,如果是單個javabean,則通過metaObject類轉換成一個BeanWrapper,進行取值

這段代碼也就負責對預編譯后的sql設置參數,這里邏輯主要是圍繞以下步驟進行得,

    獲取參數名,獲取參數值,獲取參數類型,然后做進行設值操作

 

 1  /**
 2  * mybatis數據處理有單結果集和多結果集處理,一般多結果集出現存儲過程中,如果存儲過程中寫了兩條select語句,如
 3  * select * from user , select * from classes   這種情況這里不做介紹,因為本人用的不多,理解的也不是很透徹。
 4  * 這里不多做介紹,這里只針對簡單映射做一個大概介紹
 5  *
 6  */
 7 public List<Object> handleResultSets(Statement stmt) throws SQLException {
 8     ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
 9     // 保存查詢結果
10     final List<Object> multipleResults = new ArrayList<>();
11 
12     int resultSetCount = 0;
13     // 獲取第一條數據
14     ResultSetWrapper rsw = getFirstResultSet(stmt);
15     // 如果不是多結果集映射,一般resultMaps的大小為1
16     // resultMap中存儲的有類的字段屬性,數據庫字段名稱等信息
17     List<ResultMap> resultMaps = mappedStatement.getResultMaps();
18     int resultMapCount = resultMaps.size();
19     // 校驗數據的正確性
20     validateResultMapsCount(rsw, resultMapCount);
21     while (rsw != null && resultMapCount > resultSetCount) {
22       ResultMap resultMap = resultMaps.get(resultSetCount);
23       // 處理結果集映射
24       handleResultSet(rsw, resultMap, multipleResults, null);
25       rsw = getNextResultSet(stmt);
26       cleanUpAfterHandlingResultSet();
27       resultSetCount++;
28     }
29     // 處理slect 標簽的resultSets屬性,多個用逗號隔開,個人幾乎沒用過,略過
30     String[] resultSets = mappedStatement.getResultSets();
31     if (resultSets != null) {
32       while (rsw != null && resultSetCount < resultSets.length) {
33         ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
34         if (parentMapping != null) {
35           String nestedResultMapId = parentMapping.getNestedResultMapId();
36           ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
37           handleResultSet(rsw, resultMap, null, parentMapping);
38         }
39         rsw = getNextResultSet(stmt);
40         cleanUpAfterHandlingResultSet();
41         resultSetCount++;
42       }
43     }
44 
45     return collapseSingleResultList(multipleResults);
46   }

 

以上代碼就是為結果映射做一個鋪墊,重點是在hanleResultSet方法里,

 1 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
 2     try {// 針對簡單映射,parentMapping是為Null的
 3       if (parentMapping != null) {
 4         handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
 5       } else {
 6         // 默認使用defaultResultHandler,如需使用自定義的,則可在傳參加入resultHandler接口實現類
 7         if (resultHandler == null) {
 8           DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
 9           // 處理結果,結果存在resultHandler里
10           handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
11           multipleResults.add(defaultResultHandler.getResultList());
12         } else {
13           handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
14         }
15       }
16     } finally {
17       // issue #228 (close resultsets)
18       closeResultSet(rsw.getResultSet());
19     }
20   }
public 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);
    }
  }

 

 1 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
 2       throws SQLException {
 3     DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
 4     ResultSet resultSet = rsw.getResultSet();
 5     // 跳過多少行,到達指定記錄位置,如在傳參的時候傳入了rowBounds,則會根據該類的offset值跳到指定記錄位置
 6     skipRows(resultSet, rowBounds);
 7     // shouldProcessMoreRows 用來檢測是否能繼續對后續的結果進行映射
 8     while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
 9       //用來處理resultMap節點中配置了discriminator節點,這里忽略掉
10       ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
11       // 得到的結果就是sql執行后的一行記錄,如返回User對象信息,則rowValue就代表一個user實例,里面已經有值了
12       Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
13       //保存數據
14       storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
15     }
16   }
 1 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
 2     final ResultLoaderMap lazyLoader = new ResultLoaderMap();
 3     // 創建對象,可以理解為對resultMap節點的type屬性值,進行了反射處理,得到了一個對象,但屬性值都是默認值。
 4     Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
 5     if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
 6       final MetaObject metaObject = configuration.newMetaObject(rowValue);
 7       boolean foundValues = this.useConstructorMappings;
 8       //是否需要自動映射,有三種映射,分別為None,partial,full,默認第二種,處理非嵌套映射,可通過autoMappingBehavior 配置
 9       if (shouldApplyAutomaticMappings(resultMap, false)) {
10         // 映射resultMap中未明確指定的列,如類中含有username屬性,但是resultMap中沒配置,則通過這個進行數據映射,還是可以查詢到結果
11         foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
12       }
13       // 處理resultMap中指定的列
14       foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
15       foundValues = lazyLoader.size() > 0 || foundValues;
16       // 如果沒查詢到結果,但配置可返回空對象(指的是沒有設置屬性值得對象),則返回空對象,否則返回null
17       rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
18     }
19     return rowValue;
20   }

 

這里只介紹resultMap中有明確指定的列

 1 private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
 2       throws SQLException {
 3      // 獲取數據字段名
 4     final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
 5     boolean foundValues = false;
 6     // 獲取的數據就是resultMap節點中配置的result節點,有多個result節點,這個集合大小就是多少
 7     // 里面存儲的是屬性名/字段名等信息
 8     final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
 9     for (ResultMapping propertyMapping : propertyMappings) {
10       String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
11       // 是否有嵌套映射
12       if (propertyMapping.getNestedResultMapId() != null) {
13         // the user added a column attribute to a nested result map, ignore it
14         column = null;
15       }
16       // 針對1來說一般常與嵌套查詢配合使用
17       // 2 判斷屬性基本映射
18       // 3 多結果集的一個處理
19       if (propertyMapping.isCompositeResult()// 1
20           || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))// 2
21           || propertyMapping.getResultSet() != null) {// 3
22         // 獲取當前column字段對於的值,有用到typeHandler來進行參數的一個轉換
23         Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
24         
25         //獲取類的屬性字段名
26         final String property = propertyMapping.getProperty();
27         if (property == null) {
28           continue;
29         } else if (value == DEFERRED) {// 類似占位符。處理懶加載數據
30           foundValues = true;
31           continue;
32         }
33         if (value != null) {
34           foundValues = true;
35         }
36         if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
37           // 進行設置屬性值
38           metaObject.setValue(property, value);
39         }
40       }
41     }
42     return foundValues;
43   }

 

或許有人奇怪為啥沒看到查詢的對象有set操作,值就到了對象里面去了,這里全是metaObject給你操作了,具體的,大家可以自行了解這個類,只能說這個類的功能很強大。

以上就是本文全部內容,

--------------------------------------------------------------------------------------------------------------------------分界線----------------------------------------------------------------------------------------------

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM