SqlSession執行Mapper過程
- 概述
- Mapper接口的注冊過程
-
- MapperRegistry
- MapperProxyFactory
- MapperProxy
- MappedStatement注冊過程
-
- 1.XMLConfigBuilder#mapperElement
- 2.XMLConfigBuilder#parse
- 3.XMLConfigBuilder#configurationElement
- 4.XMLMapperBuilder#buildStatementFromContext
- 5.XMLStatementBuilder#parseStatementNode
- Mapper方法調用過程(待完善)
-
- cachedMapperMethod
- MapperMethod類
- SqlCommand類
- SqlCommand#resolveMappedStatement
- MethodSignature
- ParamNameResolve
- Mapper方法的執行
- MapperMethod#execute
- SqlSession執行Mapper過程
-
- 1.DefaultSqlSession#selectList
- 2.BaseExecutor#query
- 3.BaseExecutor#queryFromDatabase
- 4.SimpleExecutor#doQuery
- 5.SimpleExecutor#prepareStatement
- 6.PreparedStatementHandler#query
- 7.DefaultResultSetHandler#handleResultSets
概述
Mapper由兩部分組成,分别為 Mapper接口 和 通過注解或者XML檔案 配置的 SQL語句
将SqlSession執行Mapper過程拆解為4部分
- Mapper接口的注冊過程
- MappedStatement對象 的 注冊過程
- Mapper方法 的 調用過程
- SqlSession執行Mapper 的過程
Mapper接口的注冊過程
Mapper接口 用于定義 執行SQL語句相關的方法,方法名一般和Mapper XML配置檔案中<select|update|delete|insert>标簽的id屬性相同
接口的完全限定名 一般對應 Mapper XML配置檔案的命名空間
Mybatis-SqlSession的建立過程中提到了DefaultSqlSession類中有Configuration和Executor成員變量
通過DefaultSqlSession#getMapper方法得到Mapper對象
實際上執行的是configuration#getMapper
而configuration#getMapper中調用的是mapperRegistry#getMapper方法
MapperRegistry
public class MapperRegistry {
// Configuration 對象引用
private final Configuration config;
// 用于注冊 (Mapper接口 對應的 Class對象) 和 MapperProxyFactory對象 的對應關系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 重要!!!
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 重要!!! 建立一個MapperProxyFactory對象
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
// ...
}
MapperRegistry#getMapper方法,能夠根據 Mapper接口的Class對象 擷取 對應的MapperProxyFactory對象
然後就可以 使用MapperProxyFactory對象 建立 MapperProxy-動态代理對象了
MapperRegistry類有一個knownMappers屬性,用于 注冊(動詞) (Mapper接口 對應的 Class對象) 和MapperProxyFactory對象 之間的關系
MapperRegistry#addMapper方法,用于向knownMappers屬性中注冊Mapper接口資訊
在
addMapper
方法中,為每個(Mapper接口對應的Class對象) 建立一個 MapperProxyFactory對象,然後添加到knownMappers屬性中
MyBatis架構 在應用啟動時 會解析 所有的Mapper接口,然後 調用MapperRegistry對象的
addMapper
方法 将 Mapper接口資訊 和 對應的MapperProxyFactory對象 注冊到 MapperRegistry對象中,路徑如下(後續完善時序圖):
- SqlSessionFactoryBuilder#build
- new XMLConfigBuilder parser
- XMLConfigBuilder#parse
- XMLConfigBuilder#parseConfiguration
- XMLConfigBuilder#mapperElement
- Configuration#addMappers、Configuration#addMapper
- MapperRegistry.addMapper
MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
// 重要!!!
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
// 重要!!!
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 執行上面的那個newInstance
return newInstance(mapperProxy);
}
}
MapperProxyFactory類的工廠方法newInstance()是非靜态的
也就是說,使用MapperProxyFactory 建立 Mapper動态代理對象 首先需要 建立MapperProxyFactory執行個體(by MapperRegistry#addMapper) -> MapperProxyFactory執行個體是什麼時候建立?
MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// ...
}
MapperProxy使用的是JDK内置的動态代理,實作了InvocationHandler接口,
invoke
方法中 為 通用的 攔截邏輯
定義方法執行攔截邏輯後,還需要調用java.lang.reflect.Proxy類的
newProxyInstance
方法 建立 代理對象(MapperProxy)
MyBatis對這一過程做了封裝,使用MapperProxyFactory建立Mapper(的)動态代理對象MapperProxy,就是上面的内容
MappedStatement注冊過程
MyBatis 通過 MappedStatement類 描述 Mapper的SQL配置資訊
SQL配置有兩種方式:一種是通過XML檔案配置;另一種是通過Java注解
而Java注解的本質就是一種輕量級的配置資訊
Configuration類中有一個mappedStatements屬性,該屬性 用于 注冊 MyBatis中所有的MappedStatement對象
mappedStatements屬性是一個Map對象,它的Key為Mapper SQL配置的Id
如果SQL是通過XML配置的,則Id為命名空間加上<select|update|delete|insert>标簽的Id
如果SQL通過Java注解配置,則Id為Mapper接口的完全限定名(包括包名)加上方法名稱
Configuration類中提供了一個
addMappedStatement
方法,用于将MappedStatement對象添加到mappedStatements屬性中
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
MappedStatement對象的建立過程 必須重點關注 <mappers>标簽的解析過程
<mappers>标簽是通過XMLConfigBuilder類的
mapperElement
方法來解析的
1.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
// 這裡
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 這裡
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
如上面的代碼所示,在
mapperElement
方法中,首先擷取<mappers>所有子标簽(<mapper>标簽或<package>标簽),然後根據不同的标簽做不同的處理
<mappers>标簽配置Mapper資訊有以下幾種方式:
Mapper SQL配置檔案的解析需要借助XMLMapperBuilder對象
在
mapperElement
方法中,首先建立一個XMLMapperBuilder對象
然後調用XMLMapperBuilder對象的
parse
方法完成解析
2.XMLConfigBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
上面的代碼中,首先調用XPathParser對象的
evalNode
方法擷取根節點對應的XNode對象
接着調用
configurationElement
方法對Mapper配置内容做進一步解析
3.XMLConfigBuilder#configurationElement
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 這裡
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
configurationElement
方法中,對Mapper SQL配置檔案的所有标簽進行解析
關注<select|insert|update|delete>标簽的解析
在上面的代碼中,擷取<select|insert|update|delete>标簽節點對應的XNode對象後,調用XMLMapperBuilder類的
buildStatementFromContext
方法做進一步解析處理
4.XMLMapperBuilder#buildStatementFromContext
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
<select|insert|update|delete>标簽的解析 需要依賴于 XMLStatementBuilder對象
XMLMapperBuilder類的
buildStatementFromContext
方法中對所有XNode對象進行周遊
然後為每個<select|insert|update|delete>标簽 對應的XNode對象 建立一個 XMLStatementBuilder對象
接着調用XMLStatementBuilder對象的
parseStatementNode
方法進行解析處理
5.XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
XMLStatementBuilder類的parseStatementNode()方法的内容相對較多,主要做了以下幾件事情:
- 擷取<select|insert|delete|update>标簽的所有屬性資訊
- 将<include>标簽引用的SQL片段替換為對應的标簽中定義的内容
- 擷取lang屬性指定的LanguageDriver,通過LanguageDriver建立SqlSource。MyBatis中的SqlSource表示一個SQL資源
- 擷取KeyGenerator對象,KeyGenerator的不同執行個體代表不同的主鍵生成政策
- 所有解析工作完成後,使用MapperBuilderAssistant對象的
方法建立MappedStatement對象。建立完成後,調用Configuration對象的addMappedStatement
方法将MappedStatement對象 注冊到 Configuration對象中(這一處理在MapperBuilderAssistant#addMappedStatement方法中)addMappedStatement
需要注意的是,MapperBuilderAssistant是一個輔助工具類,用于建構Mapper相關的對象,例如Cache、ParameterMap、ResultMap等
Mapper方法調用過程(待完善)
為了執行Mapper接口中定義的方法,首先需要調用SqlSession對象的getMapper()方法擷取一個動态代理對象,然後通過 代理對象 調用方法即可
在MapperProxy類的
invoke
方法中,對 從Object類 繼承來的方法 不做任何處理,對Mapper接口中定義的方法,調用
cachedMapperMethod
方法擷取一個MapperMethod對象
cachedMapperMethod
cachedMapperMethod()方法中對MapperMethod對象做了緩存,首先從緩存中擷取,如果擷取不到,則建立MapperMethod對象,然後添加到緩存中,這是享元思想的應用,避免頻繁建立和回收對象
MapperMethod類
在MapperMethod構造方法中建立了一個SqlCommand對象和一個MethodSignature對象:SqlCommand對象用于擷取SQL語句的類型、Mapper的Id等資訊;MethodSignature對象用于擷取方法的簽名資訊,例如Mapper方法的參數名、參數注解等資訊
SqlCommand類
SqlCommand構造方法中調用resolveMappedStatement()方法,根據Mapper接口的完全限定名和方法名擷取對應的MappedStatement對象,然後通過MappedStatement對象擷取SQL語句的類型和Mapper的Id
SqlCommand#resolveMappedStatement
首先将接口的完全限定名和方法名進行拼接,作為Mapper的Id從Configuration對象中查找對應的MappedStatement對象,如果查找不到,則判斷該方法是否是從父接口中繼承的,如果是,就以父接口作為參數遞歸調用resolveMappedStatement()方法,若找到對應的MappedStatement對象,則傳回該對象,否則傳回null
SqlCommand對象封裝了SQL語句的類型和Mapper的Id
MethodSignature
MethodSignature構造方法中隻做了3件事情:
(1)擷取Mapper方法的傳回值類型,具體是哪種類型,通過boolean類型的屬性進行标記。例如,當傳回值類型為void時,returnsVoid屬性值為true,當傳回值類型為List時,将returnsMap屬性值設定為true。MethodSignature類中标記Mapper傳回值類型的屬性如下
2)記錄RowBounds參數位置,用于處理後續的分頁查詢,同時記錄ResultHandler參數位置,用于處理從資料庫中檢索的每一行資料。
(3)建立ParamNameResolver對象。ParamNameResolver對象用于解析Mapper方法中的參數名稱及參數注解資訊
ParamNameResolve
在ParamNameResolver構造方法中,對所有Mapper方法的所有參數資訊進行周遊,首先判斷參數中是否有@Param注解,如果包含@Param注解,就從注解中擷取參數名稱,如果參數中沒有@Param注解,就根據MyBatis主配置檔案中的useActualParamName參數确定是否擷取實際方法定義的參數名稱,若useActualParamName參數值為true,則使用方法定義的參數名稱。解析完畢後,将參數資訊儲存在一個不可修改的names屬性中,該屬性是一個SortedMap<Integer, String>類型的對象。
到此為止,整個MapperMethod對象的建立過程已經完成
Mapper方法的執行
MapperMethod提供了一個execute()方法,用于執行SQL指令
在MapperProxy類的invoke()方法中擷取MapperMethod對象後,最終會調用MapperMethod類的execute()
MapperMethod#execute
在execute()方法中,首先根據SqlCommand對象擷取SQL語句的類型,然後根
據SQL語句的類型調用SqlSession對象對應的方法。例如,當SQL語句類型為INSERT時,通過SqlCommand對象擷取Mapper的Id,然後調用SqlSession對象的insert()方法。MyBatis通過動态代理将Mapper方法的調用轉換成通過SqlSession提供的API方法完成資料庫的增删改查操作,即舊的iBatis架構調用Mapper的方式
SqlSession執行Mapper過程
MyBatis通過 動态代理 将Mapper方法的調用 轉換為 調用SqlSession提供的增删改查方法
以Mapper的Id作為參數,執行資料庫的增删改查操作
1.DefaultSqlSession#selectList
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在DefaultSqlSession的
selectList
方法中,首先根據Mapper的Id從Configuration對象中擷取對應的MappedStatement對象
然後以MappedStatement對象作為參數,調用Executor執行個體的
query
方法完成查詢操作
2.BaseExecutor#query
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
在BaseExecutor類的
query
方法中,首先從MappedStatement對象中擷取BoundSql對象
BoundSql類中 封裝了 經過解析後的SQL語句 及 參數映射資訊
然後建立CacheKey對象,該對象用于緩存的Key值
接着調用重載的
query
方法
@SuppressWarnings("unchecked")
@Override
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) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
在重載的query()方法中,首先從MyBatis一級緩存中擷取查詢結果,如果緩存中沒有,則調用BaseExecutor類的
queryFromDatabase
方法從資料庫中查詢
3.BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
在queryFromDatabase()方法中,調用
doQuery
方法進行查詢,然後将查詢結果進行緩存
doQuery
是一個模闆方法,由BaseExecutor子類實作
Executor有幾個不同的實作,分别為BatchExecutor、SimpleExecutor和ReuseExecutor
4.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);
}
}
在SimpleExecutor類的
doQuery
方法中,首先調用Configuration對象的
newStatementHandler
方法建立StatementHandler對象
newStatementHandler
方法傳回的是RoutingStatementHandler的執行個體
在RoutingStatementHandler類中,會根據配置Mapper時statementType屬性指定的StatementHandler類型建立對應的StatementHandler執行個體進行處理
例如statementType屬性值為SIMPLE時,則建立SimpleStatementHandler執行個體
StatementHandler對象建立完畢後,接着調用SimpleExecutor類的
prepareStatement
方法建立JDBC中的Statement對象,然後為Statement對象設定參數操作
Statement對象初始化工作完成後,再調用StatementHandler的
query
方法執行查詢操作
5.SimpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
在SimpleExecutor類的
prepareStatement
方法中,首先擷取JDBC中的Connection對象
然後調用StatementHandler對象的
prepare
方法建立Statement對象
接着調用StatementHandler對象的
parameterize
方法(parameterize()方法中會使用ParameterHandler為Statement對象設定參數)
MyBatis的StatementHandler接口有幾個不同的實作類,分别為SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler
MyBatis預設情況下會使用PreparedStatementHandler與資料庫互動
6.PreparedStatementHandler#query
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
在PreparedStatementHandler的
query
方法中,首先調用PreparedStatement對象的
execute
方法執行SQL語句
然後調用ResultSetHandler的
handleResultSets
方法處理結果集
ResultSetHandler隻有一個預設的實作,即DefaultResultSetHandler類
7.DefaultResultSetHandler#handleResultSets
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
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);
}
DefaultResultSetHandler類的handleResultSets()方法具體邏輯如下:
- 首先從Statement對象中擷取ResultSet對象,然後 将ResultSet 包裝為 ResultSetWrapper對象,通過ResultSetWrapper對象 能夠更友善地 擷取 資料庫字段名稱 以及 字段對應的TypeHandler資訊
- 擷取Mapper SQL配置中 通過resultMap屬性 指定的 ResultMap資訊,一條SQL Mapper配置一般隻對應一個ResultMap
- 調用
方法對ResultSetWrapper對象進行處理,将結果集轉換為Java實體對象,然後将 生成的實體對象 存放在 multipleResults清單中handleResultSet
- 調用
方法對multipleResults進行處理,如果隻有一個結果集,就傳回結果集中的元素,否則傳回多個結果集collapseSingleResultList