關於Mybatis中,selectOne/selectList中statement對mapper檔案中的id匹配方式的研究
前言:
在mybatis中,對映檔案中的namespace是用於繫結Dao介面的,即面向介面程式設計。
當你的namespace繫結介面後,你可以不用寫介面實現類,mybatis會通過該繫結自動幫你找到對應要執行的SQL語句。
但是,在實際程式設計過程中,也可以使用實體類的class名稱作為namespace進行匹配。
正文:
話不多,先上程式碼:
SqlSession session = this.sqlSessionFactory.openSession(); Map paramMap = new HashMap(16); try { ZoneId zoneId = ZoneId.systemDefault(); paramMap.put("startTime", Date.from(startTime.atZone(zoneId).toInstant())); paramMap.put("endTime", Date.from(endTime.atZone(zoneId).toInstant())); Map aMap= session.selectOne("selectAMap", paramMap); return aMap; } catch (Exception e) { e.printStackTrace(); return null; } finally { if (null != session) { session.close(); } }
編寫完這段程式碼後,就想起之前想過研究下這裡是selectOne裡面的statement引數中指定的這個id是如何跟對應Mapper.xml中的id匹配起來的,這裡猜測可能是根據namespace進行匹配,遂跟進程式碼。
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { List var6; try { MappedStatement ms = this.configuration.getMappedStatement(statement); List<E> result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); var6 = result; } catch (Exception var10) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10); } finally { ErrorContext.instance().reset(); } return var6; }
這段程式碼中,發現MappedStatement類,emmm…應該在在這裡做的處理,點進去看。
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { this.buildAllStatements(); } return (MappedStatement)this.mappedStatements.get(id); }
buildAllStatements()此方法進行語句構建,再返回語句。重點來了,這裡的mappedStatements例項是一個Map物件(Configuration.StrictMap),泛型為<String, MappedStatement
protected final Map<String, MappedStatement> mappedStatements;
嗯,應該是根據傳入的id到此Map中去獲取對應的MappedStatement物件,遂檢視此Map物件的初始化方法。搜了一下,應該是這裡。
public void addMappedStatement(MappedStatement ms) {
this.mappedStatements.put(ms.getId(), ms);
}
打上斷點,重啟專案,bingo!捕獲到了,F8走起,發現這裡有一個迴圈,具體迭代裡面的東西怎麼來的就沒看了,但是可以確定是掃描了專案中所有的Mapper檔案得來的所有Mapper.xml中的id,並且這裡是在前面加上了namespace的。
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId) {
id = this.applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType);
statementBuilder.resource(this.resource);
statementBuilder.fetchSize(fetchSize);
statementBuilder.statementType(statementType);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
statementBuilder.keyColumn(keyColumn);
statementBuilder.databaseId(databaseId);
this.setStatementTimeout(timeout, statementBuilder);
this.setStatementParameterMap(parameterMap, parameterType, statementBuilder);
this.setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
this.setStatementCache(isSelect, flushCache, useCache, this.currentCache, statementBuilder);
MappedStatement statement = statementBuilder.build();
this.configuration.addMappedStatement(statement);
return statement;
}
接下來就是迭代迴圈時,發現一個有趣的現象。
public void addMappedStatement(MappedStatement ms) {
this.mappedStatements.put(ms.getId(), ms);
}
這裡put方法,每次會放兩個值到這個Map中,感到疑惑,點進去發現,如果id帶namespace也就是帶**.**,裡面會將此id處理為兩個字串,一個是原本帶namespace的,一個是經過擷取只剩下最後的id的字串,如com.abc.scInstance,這裡產生兩個字串->{
com.abc.scInstance,
scInstance
}
得出結論,return (MappedStatement)this.mappedStatements.get(id);
這裡的id不帶namespace也是可以在mappedStatements中獲取到對應的MappedStatement物件,進行匹配到對應的帶有namespace的id的語句。
而為什麼是要兩個字串,裡面存相同資料,那應該是就是當不同Mapper檔案中存在相同id時用於區分了,畢竟加上實體類的namespace得到的必定是唯一的語句。具體原因後續再研究下寫上來。
總結:
作為新手的第一次原創原始碼解析,感覺自己的完全沒有講清楚的,如果有大牛路過,希望給出指導,在下感激不盡。 --寫於踏上工作崗位的3個月後…