mybatis查詢語句揭秘之封裝數(shù)據(jù)
一、前言
繼上一篇mybatis查詢語句的背后,這一篇主要圍繞著mybatis查詢的后期操作,即跟數(shù)據(jù)庫交互的時(shí)候。由于本人也是一邊學(xué)習(xí)源碼一邊記錄,內(nèi)容難免有錯(cuò)誤或不足之處,還望諸位指正,本文只可當(dāng)參考作用。謹(jǐn)記!
二、分析
繼上一篇博文的查詢例子,mybatis在最后的查詢最終會(huì)走SimpleExecutor類的doQuery方法,
@Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); // 這里也就是采用了策略模式(個(gè)人感覺有點(diǎn)像),實(shí)際的statementHandler為routingStatementHandler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); // 雖然是執(zhí)行的routingStatementHandler.query,但返回結(jié)果的還是PreparedStatementHandler處理 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 使用了代理模式,也可以理解為對(duì)connection進(jìn)行了一層包裝,這里的作用就是加了log處理 Connection connection = getConnection(statementLog); //進(jìn)行預(yù)編譯,即類似jdbc的 sql,如 select * from user where id=? stmt = handler.prepare(connection, transaction.getTimeout()); // 對(duì)執(zhí)行查詢的sql進(jìn)行參數(shù)設(shè)置 handler.parameterize(stmt); return stmt; }
關(guān)于 handler.prepare的作用這里簡單介紹下,不做代碼分析。
會(huì)設(shè)置fetchSize,作用就是一次性從數(shù)據(jù)庫抓取數(shù)據(jù),好像默認(rèn)值是10條,如果每次只抓取一條,則進(jìn)行rs.next的時(shí)候,會(huì)再次查庫。
如果是insert操作,并且數(shù)據(jù)庫主鍵自增且還設(shè)置了可以返回主鍵,則會(huì)還做獲取主鍵的操作。
先從設(shè)置參數(shù)說起,也就是handler.parameterize。先看下源碼,具體位置在DefaultParameterHandler類里面
@Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // 獲取配置文件里面的sql參數(shù)信息,如sql為select * from user where id=#{userId,jdbcType=INTEGER} // ParameterMapping 記錄了參數(shù)名也就是userId,還有記錄了對(duì)應(yīng)的jdbc類型,還有對(duì)應(yīng)的javaType等等,具體可以debug看下 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); // 如果為true,那么sql參數(shù)中有類似 user.name 格式 if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { // metaObject 類似一個(gè)工具類,它里面有一個(gè)反射工廠,可以專門解析一個(gè)類的信息,如字段的setter/getter/屬性信息,這里不做多余介紹 // 1、下面詳細(xì)介紹 MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName);// 取值 } // 獲取對(duì)應(yīng)的typeHandler,一般情況不設(shè)置的話,基本都是ObjectTypeHandler TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { // 進(jìn)行設(shè)值 typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } catch (SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }
對(duì)于上述代碼中的一部分這里負(fù)責(zé)將parameterObject的里面的值整出來(也就是傳入的參數(shù)),如果參數(shù)是map結(jié)構(gòu),就從map里面取值,如果不是,如單個(gè)非javabean參數(shù),則直接取值,如果是單個(gè)javabean,則通過metaObject類轉(zhuǎn)換成一個(gè)BeanWrapper,進(jìn)行取值
這段代碼也就負(fù)責(zé)對(duì)預(yù)編譯后的sql設(shè)置參數(shù),這里邏輯主要是圍繞以下步驟進(jìn)行得,
獲取參數(shù)名,獲取參數(shù)值,獲取參數(shù)類型,然后做進(jìn)行設(shè)值操作
/** * mybatis數(shù)據(jù)處理有單結(jié)果集和多結(jié)果集處理,一般多結(jié)果集出現(xiàn)存儲(chǔ)過程中,如果存儲(chǔ)過程中寫了兩條select語句,如 * select * from user , select * from classes 這種情況這里不做介紹,因?yàn)楸救擞玫牟欢?,理解的也不是很透徹? * 這里不多做介紹,這里只針對(duì)簡單映射做一個(gè)大概介紹 * */ public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); // 保存查詢結(jié)果 final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; // 獲取第一條數(shù)據(jù) ResultSetWrapper rsw = getFirstResultSet(stmt); // 如果不是多結(jié)果集映射,一般resultMaps的大小為1 // resultMap中存儲(chǔ)的有類的字段屬性,數(shù)據(jù)庫字段名稱等信息 List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); // 校驗(yàn)數(shù)據(jù)的正確性 validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); // 處理結(jié)果集映射 handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } // 處理slect 標(biāo)簽的resultSets屬性,多個(gè)用逗號(hào)隔開,個(gè)人幾乎沒用過,略過 String[] resultSets = mappedStatement.getResultSets(); 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); }
以上代碼就是為結(jié)果映射做一個(gè)鋪墊,重點(diǎn)是在hanleResultSet方法里,
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try {// 針對(duì)簡單映射,parentMapping是為Null的 if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { // 默認(rèn)使用defaultResultHandler,如需使用自定義的,則可在傳參加入resultHandler接口實(shí)現(xiàn)類 if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); // 處理結(jié)果,結(jié)果存在resultHandler里 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()); } }
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); } }
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); ResultSet resultSet = rsw.getResultSet(); // 跳過多少行,到達(dá)指定記錄位置,如在傳參的時(shí)候傳入了rowBounds,則會(huì)根據(jù)該類的offset值跳到指定記錄位置 skipRows(resultSet, rowBounds); // shouldProcessMoreRows 用來檢測(cè)是否能繼續(xù)對(duì)后續(xù)的結(jié)果進(jìn)行映射 while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { //用來處理resultMap節(jié)點(diǎn)中配置了discriminator節(jié)點(diǎn),這里忽略掉 ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); // 得到的結(jié)果就是sql執(zhí)行后的一行記錄,如返回User對(duì)象信息,則rowValue就代表一個(gè)user實(shí)例,里面已經(jīng)有值了 Object rowValue = getRowValue(rsw, discriminatedResultMap, null); //保存數(shù)據(jù) storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } }
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // 創(chuàng)建對(duì)象,可以理解為對(duì)resultMap節(jié)點(diǎn)的type屬性值,進(jìn)行了反射處理,得到了一個(gè)對(duì)象,但屬性值都是默認(rèn)值。 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; //是否需要自動(dòng)映射,有三種映射,分別為None,partial,full,默認(rèn)第二種,處理非嵌套映射,可通過autoMappingBehavior 配置 if (shouldApplyAutomaticMappings(resultMap, false)) { // 映射resultMap中未明確指定的列,如類中含有username屬性,但是resultMap中沒配置,則通過這個(gè)進(jìn)行數(shù)據(jù)映射,還是可以查詢到結(jié)果 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } // 處理resultMap中指定的列 foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; // 如果沒查詢到結(jié)果,但配置可返回空對(duì)象(指的是沒有設(shè)置屬性值得對(duì)象),則返回空對(duì)象,否則返回null rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; }
這里只介紹resultMap中有明確指定的列
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { // 獲取數(shù)據(jù)字段名 final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; // 獲取的數(shù)據(jù)就是resultMap節(jié)點(diǎn)中配置的result節(jié)點(diǎn),有多個(gè)result節(jié)點(diǎn),這個(gè)集合大小就是多少 // 里面存儲(chǔ)的是屬性名/字段名等信息 final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); // 是否有嵌套映射 if (propertyMapping.getNestedResultMapId() != null) { // the user added a column attribute to a nested result map, ignore it column = null; } // 針對(duì)1來說一般常與嵌套查詢配合使用 // 2 判斷屬性基本映射 // 3 多結(jié)果集的一個(gè)處理 if (propertyMapping.isCompositeResult()// 1 || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))// 2 || propertyMapping.getResultSet() != null) {// 3 // 獲取當(dāng)前column字段對(duì)于的值,有用到typeHandler來進(jìn)行參數(shù)的一個(gè)轉(zhuǎn)換 Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); //獲取類的屬性字段名 final String property = propertyMapping.getProperty(); if (property == null) { continue; } else if (value == DEFERRED) {// 類似占位符。處理懶加載數(shù)據(jù) foundValues = true; continue; } if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) { // 進(jìn)行設(shè)置屬性值 metaObject.setValue(property, value); } } } return foundValues; }
或許有人奇怪為啥沒看到查詢的對(duì)象有set操作,值就到了對(duì)象里面去了,這里全是metaObject給你操作了,具體的,大家可以自行了解這個(gè)類,只能說這個(gè)類的功能很強(qiáng)大。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
解決java.lang.NullPointerException報(bào)錯(cuò)以及分析出現(xiàn)的幾種原因
這篇文章介紹了解決java.lang.NullPointerException報(bào)錯(cuò)的方法,以及分析出現(xiàn)的幾種原因。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12Java中jdk1.8和jdk17相互切換實(shí)戰(zhàn)步驟
之前做Java項(xiàng)目時(shí)一直用的是jdk1.8,現(xiàn)在想下載另一個(gè)jdk版本17,并且在之后的使用中可以進(jìn)行相互切換,下面這篇文章主要給大家介紹了關(guān)于Java中jdk1.8和jdk17相互切換的相關(guān)資料,需要的朋友可以參考下2023-05-05教你用Java Swing實(shí)現(xiàn)自助取款機(jī)系統(tǒng)
今天給大家?guī)淼氖顷P(guān)于JAVA的相關(guān)知識(shí),文章圍繞著如何用Java Swing實(shí)現(xiàn)自助取款機(jī)系統(tǒng)展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06Java使用Jedis操作Redis服務(wù)器的實(shí)例代碼
本篇文章主要介紹了Java使用Jedis操作Redis服務(wù)器的實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08java實(shí)現(xiàn)簡單學(xué)生管理系統(tǒng)項(xiàng)目
這篇文章主要介紹了java實(shí)現(xiàn)簡單學(xué)生管理系統(tǒng)項(xiàng)目,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07SpringCloud OpenFeign 參數(shù)傳遞和響應(yīng)處理的詳細(xì)步驟
本文給大家講解SpringCloud OpenFeign 參數(shù)傳遞和響應(yīng)處理的詳細(xì)步驟,本文給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-02-02