Mybatis中擷取擷取代理對象的之後的執行操作
之前已經說了Mybatis中mapper是怎麼和XMl關聯起來的已經分析了,Mybaits從XML是怎麼和Mapper對應上來的。這裡從宏觀上介紹一下是怎麼執行的。之後會在具體的部落格裡面介紹。
1. 幾個重要的步驟
- 調用方法的時候調用
的MapperProxy
方法,并且建構invoke
調用MapperMethodInvoker
方法,然後會調用invoke
方法的MapperMethod
方法。execute
- 組裝參數,将方法的參數組成Map,Map的key是String,Value是具體的參數值(ParamNameResolver)
- 調用sqlSession的select方法
- 通過StatementId(全限定類名+方法名)從Configuration裡面解析
從下面開始,都是在Executor裡面運作的。MappedStatement
- 通過指定器(Executor)執行
方法,在這裡面建構查詢所需要的一切操作。并且執行查詢操作,将查詢的結果封裝實體類,傳回query
- 建構BoundSql對象(通過參數和原始的sql來解析動态sql,生成用于PrepareStatement裡面的sql)。
select * from t_class where id = ? and name in ?
- 建構
(用作緩存,通過sql,同樣的參數,同樣的offset或者limit)CacheKey
- 先從二級緩存中查找(如果指定開啟)
- 再從一級緩存中查找(這肯定是開啟的)
- 建立
在建立它的時候。會建立StatementHandler
和ParameterHandler
,并且調用插件方法包裝。ResultSetHandler
- 調用
的StatementHandler
方法prepare
- 将之前
對象解析好sql設定給ConnectionBoundSql
(connection.prepareStatement(sql))
- 給查詢設定設定逾時時間和
fetchSize
- 調用
來給設定此次查詢的參數。ParameterHandler
- 執行查詢。
- 調用
來處理結果映射ResultSetHandler
- 将之前
- 傳回結果,并且将結果放在一級緩存裡面。
- 如果條件滿足,也放在二級緩存裡面。
那麼下面就簡單的來看一下大體的流程吧。之後會圍繞具體的方面來分析,流程圖就不畫了,上面的文字寫的也很清楚了。
測試案例
@BeforeAll static void setup() throws Exception { //這裡隻是為了測試友善,運作了測試的腳本。這裡的dataSource和MapperConfig裡面的沒有關系。 createStudentDataSource(); //這就是擷取連接配接,執行腳本。 final String resource = "org/apache/ibatis/builder/MapperConfig.xml"; final Reader reader = Resources.getResourceAsReader(resource); // 加載配置檔案,解析mapper,mapper裡面的每個方法都對應一個MapperStatement。大體就是這樣,但是具體的實作就複雜了,比如動态sql,mybatis的配置資訊。 //所有的資訊都關聯到 SqlSessionFactoryBuilder裡面的Configuration實體類。包括mapper,setting,還有代理對象的建立 sqlMapper = new SqlSessionFactoryBuilder().build(reader); } @Test public void testShouldSelectClassUsingMapperClass(){ try( SqlSession session = sqlMapper.openSession() ){ ClassMapper mapper = session.getMapper(ClassMapper.class); long start = System.currentTimeMillis(); System.out.println(mapper.listClassOwnerStudentByClassId(1,new String[]{"狗蛋"})); System.out.println("start:" + (System.currentTimeMillis()-start)); long start1 = System.currentTimeMillis(); System.out.println(mapper.listClassOwnerStudentByClassId(2,new String[]{"狗蛋"})); System.out.println("start1:" + (System.currentTimeMillis()-start1)); long start2 = System.currentTimeMillis(); System.out.println(mapper.listClassOwnerStudentByClassId(1,new String[]{"狗蛋"})); System.out.println("start2:" + (System.currentTimeMillis()-start2)); session.commit(); } }
- 建構BoundSql對象(通過參數和原始的sql來解析動态sql,生成用于PrepareStatement裡面的sql)。
調用MethodProxy,調用Invoke方法,并且組裝參數
// MapperProxy的Invoke方法,這裡會建構MapperMethodInvoker大多數的是PlainMethodInvoker。但是對于default修飾的方法
//是 DefaultMethodInvoker,
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {//如果是object類裡面的方法,直接調用就好了
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//MapperMethod,通過之前建構的時候确認的類型來判斷,這裡的模式美其名曰,政策模式
// 這裡我用SELECT方法舉例,
// 在這個分支裡面會根據傳回值的類型不同,來約定傳回方法分支。
// 下面的代碼是按照method.returnsMany()來的。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result; //美其名曰,政策模式
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
}
組裝參數,将方法的參數組成Map,Map的key是String,Value是具體的參數值(ParamNameResolver)
在組裝MapperMethod的時候會建構
MethodSignature
,這裡面會設定
ParamNameResolver
,在從上面的代碼往下走的時候,會通過
ParamNameResolver
來解析參數,即就是組裝成map。
// ParamNameResolver中的getNamedParams方法
// 這裡的方法能解釋在Mapper裡面可以用Mybatis提供的預設參數。
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) { //這裡就可以看到,會給參數設定一個預設的值,如果不設定的話,并且還會生成按照下标會同時會生成GENERIC_NAME_PREFIX字首,同時也會放在map裡面傳回
Object value = args[names.firstKey()]; //這裡如果隻有一個參數,并且它還沒有标注param注解,下面的這個方法,就很簡單,如果是collection或者array的話,存放預設值,如果不是,就把原來的參數值直接傳回
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
通過StatementId(全限定類名+方法名)從Configuration裡面解析 MappedStatement
MappedStatement
// DefaultSqlSession中的selectList。
// 從configuration的MapperStatement通過StatementId擷取MapperStatement。
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try { //拿到mapperStatement,這個是在xml中解析mapper檔案的時候解析出來的
MappedStatement ms = configuration.getMappedStatement(statement);
// 查詢交給Executor來執行。
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);// 這裡就很離譜,居然還再次包裝了一下下。很離譜,對應Collection或者Array
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
建構BoundSql對象(通過參數和原始的sql來解析動态sql,生成用于PrepareStatement裡面的sql)。
如果開啟了二級緩存
DefaultSqlSession
中的
Executor
是
CachingExecutor
,如果沒有開啟就是直接是
SimpleExecutor
// ExecutorType有三種,SIMPLE,REUSE,BATCH,一般來說,都是simple。并且它預設的執行器的類型就是simple
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//如果開啟了緩存就用CachingExecutor來包裝之前搞好的Executor,
// CachingExecutor裡面有一個TransactionalCacheManager,這是mybatis實作二級緩存的重點。
// cacheEnabled這個辨別位。就是xml中Setting設定的。,預設是開啟,如果開啟,就将executor用CachingExecutor包裝。
// 在說一下,executorType預設是SIMPLE,并且這個也是可以通過xml中setting的defaultExecutorType來設定的。
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//在interceptorChain應用Executor。
//waring 這裡也很重要,這是是實作mybatis攔截器的主要實作。主要看這個方法,Interceptor類裡面的wrap方法
// 其實這裡就是生成了代理對象,調用 Plugin.wrap(target, this);來生成代理對象,在這裡會解析interceptor的上面的注解,組成map,key是接口的class。
// value是set,裡面存放的需要攔截的方法。如果需要生成代理對象就生成代理對象,InvocationHandler是Plugin。 具體的還得看看方法裡面是怎麼寫的。
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
通過executor來執行方法,在這個方法裡面會通過參數來解析動态sql。并且将sql中參數的位置變為
?
占位符。
//MappedStatement的 getBoundSql方法,在這裡會将之前搞好的參數(也就是那個Map)傳遞進來并且根據這些參數,來解析動态sql。
//動态sql的具體實作是 SqlNode。接口
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
建構 CacheKey
(用作緩存,通過sql,同樣的參數,同樣的offset或者limit)
CacheKey
建構緩存key,建構這樣的key的要求可多了,從代碼就可以看出,一個cachekey方方面面那是得完全一樣的呀。包括,參數,原始sql,包括sql參數的位置。我意思是比如有這樣的一個sql
select * from t_class where a = 1 and b = 2
select * from t_class where a = 2 and b = 1
這兩sql對于我們來說,結果是一樣的,但對于Mybatis來說,這可不一樣,不能共用一個CacheKey。
話說回來,這确實的一模一樣,但凡要是有一點不一樣,兩次查詢,隻因為那一點不一樣,出來的結果确實一樣,那不離譜了嗎?
// BaseExecutor的createCacheKey方法
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//方法參數的映射
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();//在這裡肯定是會用到類型處理的,
// mimic DefaultParameterHandler logic 這個parameterMappings,按照下标和?對應。下标為0的,表示為sql中第一個問号。
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();//參數名字。
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
} //上面的都是通過propertyName來擷取value。
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
先從二級緩存中查找(如果指定開啟),再從一級查詢,并且将結果放在二級和一級緩存中去。
二級緩存相關代碼
// CachingExecutor中的query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 這個緩存是在mapper.xml中配置的。在解析xml檔案的時候配置的。
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
// MapperStatement如果要配置緩存,并且resultHandler為null。就去查二級緩存。
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//從二級緩存中查詢,如果沒有再從一級緩存裡面去查,這裡的delegate就是BaseExecutor
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//查到的結果放在二級緩存中
tcm.putObject(cache, key, list);
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
一級緩存相關代碼
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
//如果需要清楚一級緩存
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 從一級緩存中查找
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 緩存中沒有就要去資料庫查詢了
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {//lcnote 這個deferredLoads是幹嘛的,
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
//如果緩存緩存的作用域是STATEMENT,在查詢結束之後會清楚緩存,這肯定是線程不安全的。有問題,如果兩個線程同時操作,一個不需要清楚緩存
// 一個需要清楚緩存,那不就得麼的了。出現問題了?
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 在一級緩存中存放執行占位符,
//為什麼要這樣做呢?如果一個
// 如果先一個查詢,後一個再來,正好中間有一個key的話。就拿到髒的資料了。也沒看到他對這個資料有什麼特殊的處理呀
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 查詢資料庫
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 移除掉之前用于占位的key
// 這裡的remove我不是很了解,與其說是占位,name在這裡直接put覆寫就好了,這裡這樣做怕是為了防止doQuery發生異常,導緻這個緩存是錯誤的,是以, 這裡必須有finally,這都是細節細節
localCache.removeObject(key);
}
localCache.putObject(key, list);//放在一級緩存中。
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
建立 StatementHandler
在建立它的時候。會建立 ParameterHandler
和 ResultSetHandler
,并且調用插件方法包裝
StatementHandler
ParameterHandler
ResultSetHandler
建立StatementHandler,并且用interceptor包裝他,關于interceptor在前面的部落格中已經介紹了。
//Configuration的newStatementHandler方法
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
建立parameterHandler和resultSetHandler并且interceptor包裝
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
得到Connection并且設定sql,和設定此次查詢的一些基本屬性
// 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();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);//查詢并且映射結果集
} finally {
closeStatement(stmt);
}
}
設定sql,設定屬性
//BaseStatementHandler的prepare方法
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//執行個體化和初始化,這裡會調用Connection的 connection.prepareStatement(sql)方法,設定原始的sql
statement = instantiateStatement(connection);
//設定statement屬性
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
調用 ParameterHandler
來給設定此次查詢的參數。
ParameterHandler
// DefaultParameterHandler的方法,
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//得到參數的映射 ,這裡會解析參數的類型,将實際參數的類型轉變為JDBC認可的類型。
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();
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 metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//在上面解析完參數并且确定好類型之後,這裡會通過實際的typeHandler來PreparedStatement對應的位置設定參數
//注意這裡的+1操作。原始的JDBC裡面設定參數是從下标1開始的。之前已經将需要查詢的sql已經設定進去了。
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
執行查詢并且傳回結果
執行查詢
//PreparedStatementHandler方法
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//waring 這裡會真正的執行sql,下面就開始處理結果集
ps.execute();
//處理結果集映射
return resultSetHandler.handleResultSets(ps);
}
傳回結果,處理結果集映射。
關于這裡的代碼,我沒有詳細的看過,之後看完之後會有詳細分析一下。
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//這個集合就是用來存放sql查詢結果之後,封裝好的對象。
final List<Object> multipleResults = new ArrayList<>();
//将結果包裝成ResultSetWrapper,統計了每一列的類型和名字,還有對應的jdbc的資料類型
int resultSetCount = 0;
// 将 statement的ResultSet包裝為ResultSetWrapper。
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
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); //将上面的改變,合并為一個單一的list。
}
Mybatis中擷取擷取代理對象的之後的執行操作就分析到這裡了。如有不正确的地方,歡迎指出。謝謝。