mybatis查詢語句的背後之封裝資料
轉載請註明出處。。。
一、前言
繼上一篇mybatis查詢語句的背後 ,這一篇主要圍繞著mybatis查詢的後期操作,即跟資料庫互動的時候。由於本人也是一邊學習原始碼一邊記錄,內容難免有錯誤或不足之處,還望諸位指正,本文只可當參考作用。謹記!
二、分析
繼上一篇博文的查詢例子,mybatis在最後的查詢最終會走SimpleExecutor類的doQuery方法,
1@Override 2public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 3Statement stmt = null; 4try { 5Configuration configuration = ms.getConfiguration(); 6// 這裡也就是採用了策略模式(個人感覺有點像),實際的statementHandler為routingStatementHandler 7StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 8stmt = prepareStatement(handler, ms.getStatementLog()); 9// 雖然是執行的routingStatementHandler.query,但返回結果的還是PreparedStatementHandler處理 10return handler.query(stmt, resultHandler); 11} finally { 12closeStatement(stmt); 13} 14} 15 16 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { 17Statement stmt; 18// 使用了代理模式,也可以理解為對connection進行了一層包裝,這裡的作用就是加了log處理 19Connection connection = getConnection(statementLog); 20//進行預編譯,即類似jdbc的 sql,如 select * from user where id=? 21stmt = handler.prepare(connection, transaction.getTimeout()); 22// 對執行查詢的sql進行引數設定 23handler.parameterize(stmt); 24return stmt; 25}
關於 handler.prepare的作用這裡簡單介紹下,不做程式碼分析。
會設定fetchSize,作用就是一次性從資料庫抓取資料,好像預設值是10條,如果每次只抓取一條,則進行rs.next的時候,會再次查庫。
如果是insert操作,並且資料庫主鍵自增且還設定了可以返回主鍵,則會還做獲取主鍵的操作。
先從設定引數說起,也就是handler.parameterize。先看下原始碼,具體位置在DefaultParameterHandler類裡面
1 @Override 2public void setParameters(PreparedStatement ps) { 3ErrorContext.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看下 6List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); 7if (parameterMappings != null) { 8for (int i = 0; i < parameterMappings.size(); i++) { 9ParameterMapping parameterMapping = parameterMappings.get(i); 10if (parameterMapping.getMode() != ParameterMode.OUT) { 11Object value; 12String propertyName = parameterMapping.getProperty(); 13// 如果為true,那麼引數中有類似 user.name 格式 14if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params 15value = boundSql.getAdditionalParameter(propertyName); 16} else if (parameterObject == null) { 17value = null; 18} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { 19value = parameterObject; 20} else { 21// metaObject 類似一個工具類,它裡面有一個反射工廠,可以專門解析一個類的資訊,如欄位的setter/getter/屬性資訊,這裡不做多餘介紹 22// 這裡負責將parameterObject的裡面的值整出來(也就是傳入的引數),實際上程式碼執行到了這,parameterObject就是一個map結構 23MetaObject metaObject = configuration.newMetaObject(parameterObject); 24value = metaObject.getValue(propertyName);// 取值 25} 26// 獲取對應的typeHandler,一般情況不設定的話,基本都是ObjectTypeHandler 27TypeHandler typeHandler = parameterMapping.getTypeHandler(); 28JdbcType jdbcType = parameterMapping.getJdbcType(); 29if (value == null && jdbcType == null) { 30jdbcType = configuration.getJdbcTypeForNull(); 31} 32try { 33// 進行設值 34typeHandler.setParameter(ps, i + 1, value, jdbcType); 35} catch (TypeException e) { 36throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); 37} catch (SQLException e) { 38throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); 39} 40} 41} 42} 43}
這段程式碼也就負責對預編譯後的sql設定引數,這裡邏輯主要是圍繞以下步驟進行得,
獲取引數名,獲取引數值,獲取引數型別,然後做進行設值操作
1/** 2* mybatis資料處理有單結果集和多結果集處理,一般多結果集出現儲存過程中,如果儲存過程中寫了兩條select語句,如 3* select * from user , select * from classes這種情況這裡不做介紹,因為本人用的不多,理解的也不是很透徹。 4* 這裡不多做介紹,這裡只針對簡單對映做一個大概介紹 5* 6*/ 7 public List<Object> handleResultSets(Statement stmt) throws SQLException { 8ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); 9// 儲存查詢結果 10final List<Object> multipleResults = new ArrayList<>(); 11 12int resultSetCount = 0; 13// 獲取第一條資料 14ResultSetWrapper rsw = getFirstResultSet(stmt); 15// 如果不是多結果集對映,一般resultMaps的大小為1 16// resultMap中儲存的有類的欄位屬性,資料庫欄位名稱等資訊 17List<ResultMap> resultMaps = mappedStatement.getResultMaps(); 18int resultMapCount = resultMaps.size(); 19// 校驗資料的正確性 20validateResultMapsCount(rsw, resultMapCount); 21while (rsw != null && resultMapCount > resultSetCount) { 22ResultMap resultMap = resultMaps.get(resultSetCount); 23// 處理結果集對映 24handleResultSet(rsw, resultMap, multipleResults, null); 25rsw = getNextResultSet(stmt); 26cleanUpAfterHandlingResultSet(); 27resultSetCount++; 28} 29// 處理slect 標籤的resultSets屬性,多個用逗號隔開,個人幾乎沒用過,略過 30String[] resultSets = mappedStatement.getResultSets(); 31if (resultSets != null) { 32while (rsw != null && resultSetCount < resultSets.length) { 33ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); 34if (parentMapping != null) { 35String nestedResultMapId = parentMapping.getNestedResultMapId(); 36ResultMap resultMap = configuration.getResultMap(nestedResultMapId); 37handleResultSet(rsw, resultMap, null, parentMapping); 38} 39rsw = getNextResultSet(stmt); 40cleanUpAfterHandlingResultSet(); 41resultSetCount++; 42} 43} 44 45return collapseSingleResultList(multipleResults); 46}
以上程式碼就是為結果對映做一個鋪墊,重點是在hanleResultSet方法裡,
1 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { 2try {// 針對簡單對映,parentMapping是為Null的 3if (parentMapping != null) { 4handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); 5} else { 6// 預設使用defaultResultHandler,如需使用自定義的,則可在傳參加入resultHandler介面實現類 7if (resultHandler == null) { 8DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); 9// 處理結果,結果存在resultHandler裡 10handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); 11multipleResults.add(defaultResultHandler.getResultList()); 12} else { 13handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); 14} 15} 16} finally { 17// issue #228 (close resultsets) 18closeResultSet(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) 2throws SQLException { 3DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); 4ResultSet resultSet = rsw.getResultSet(); 5// 跳過多少行,到達指定記錄位置,如在傳參的時候傳入了rowBounds,則會根據該類的offset值跳到指定記錄位置 6skipRows(resultSet, rowBounds); 7// shouldProcessMoreRows 用來檢測是否能繼續對後續的結果進行對映 8while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { 9//用來處理resultMap節點中配置了discriminator節點,這裡忽略掉 10ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); 11// 得到的結果就是sql執行後的一行記錄,如返回User物件資訊,則rowValue就代表一個user例項,裡面已經有值了 12Object rowValue = getRowValue(rsw, discriminatedResultMap, null); 13//儲存資料 14storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); 15} 16}
1 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { 2final ResultLoaderMap lazyLoader = new ResultLoaderMap(); 3// 建立物件,可以理解為對resultMap節點的type屬性值,進行了反射處理,得到了一個物件,但屬性值都是預設值。 4Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); 5if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { 6final MetaObject metaObject = configuration.newMetaObject(rowValue); 7boolean foundValues = this.useConstructorMappings; 8//是否需要自動對映,有三種對映,分別為None,partial,full,預設第二種,處理非巢狀對映,可通過autoMappingBehavior 配置 9if (shouldApplyAutomaticMappings(resultMap, false)) { 10// 對映resultMap中未明確指定的列,如類中含有username屬性,但是resultMap中沒配置,則通過這個進行資料對映,還是可以查詢到結果 11foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; 12} 13// 處理resultMap中指定的列 14foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; 15foundValues = lazyLoader.size() > 0 || foundValues; 16// 如果沒查詢到結果,但配置可返回空物件(指的是沒有設定屬性值得物件),則返回空物件,否則返回null 17rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; 18} 19return rowValue; 20}
這裡只介紹resultMap中有明確指定的列
1 private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) 2throws SQLException { 3// 獲取資料欄位名 4final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); 5boolean foundValues = false; 6// 獲取的資料就是resultMap節點中配置的result節點,有多個result節點,這個集合大小就是多少 7// 裡面儲存的是屬性名/欄位名等資訊 8final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); 9for (ResultMapping propertyMapping : propertyMappings) { 10String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); 11// 是否有巢狀對映 12if (propertyMapping.getNestedResultMapId() != null) { 13// the user added a column attribute to a nested result map, ignore it 14column = null; 15} 16// 針對1來說一般常與巢狀查詢配合使用 17// 2 判斷屬性基本對映 18// 3 多結果集的一個處理 19if (propertyMapping.isCompositeResult()// 1 20|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))// 2 21|| propertyMapping.getResultSet() != null) {// 3 22// 獲取當前column欄位對於的值,有用到typeHandler來進行引數的一個轉換 23Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); 24 25//獲取類的屬性欄位名 26final String property = propertyMapping.getProperty(); 27if (property == null) { 28continue; 29} else if (value == DEFERRED) {// 類似佔位符。處理懶載入資料 30foundValues = true; 31continue; 32} 33if (value != null) { 34foundValues = true; 35} 36if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) { 37// 進行設定屬性值 38metaObject.setValue(property, value); 39} 40} 41} 42return foundValues; 43}
或許有人奇怪為啥沒看到查詢的物件有set操作,值就到了物件裡面去了,這裡全是metaObject給你操作了,具體的,大家可以自行了解這個類,只能說這個類的功能很強大。
以上就是本文全部內容,
--------------------------------------------------------------------------------------------------------------------------分界線----------------------------------------------------------------------------------------------