調用流程概述:#
mybatis 的前身是 ibatis,原生的 ibatis 執行 curd 操作是固定的操作,使用 SQLSession 接口中的方法進行增刪查改,我們先閱讀 Ibatis 部分的代碼,後續 mybatis [plus] 都是在此基礎上擴展了 MapperProxy 以及預設 sql 語句動態 sql 條件等封裝,我們看一下 ibatis 的 Session 核心接口:
public interface SqlSession extends Closeable {
<E> List<E> selectList(String statement);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<T> Cursor<T> selectCursor(String statement);
int insert(String statement);
int update(String statement);
int delete(String statement);
}
這個接口裡面最終要的四類方法為(selectList、selectMap、selectCursor)、insert、delete、update。
但其實最終執行 sql 語句的為 Executor 中的方法
public interface Executor {
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
}
其中分為 query 和 update 方法,最後由實際子類的 doQuery、doUpdate 執行真正的 sql 語句,其中進一步交給了 StatementHandler 對象處理
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
void parameterize(Statement statement) throws SQLException;
void batch(Statement statement) throws SQLException;
int update(Statement statement) throws SQLException;
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(Statement statement) throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
可以看到裡面最終到達 update 或者 query 方法
所以調用邏輯為 SqlSession->Executor->StatementHandler,每個 SqlSession 有一個 Executor,每個 Executor 有一個 StatementHandler(configuration 中獲得的,經過插件的包裝,後面會說);
SqlSession 的創建過程:
其中的核心為 Configuration,整個 ibatis 都圍繞這個 Configuration 類,非常重要負責 Executor、ParameterHandler、ResultSetHandler 的創建,MapperStatements 的保存
一、二級緩存#
一级缓存
存在於執行器中,每次創建都會攜帶,所以每次會話都會有一级緩存的存在:BaseExecutor.java
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;
}
在查詢之前,會先去緩存裡面查找,其中 key 值與四個方面有關
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
只有當 sql 語句、參數、分頁、與綁定參數的 sql 一樣才會匹配;
二級緩存
存在於 ms 中的 cache 屬性當中,只有當 configuration 中的 cacheEnable 為 true 才會創建 cacheExecutor
// Configuration.java
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : 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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
// 默認為true
同時 ms 中的 useCache 為 true,才會實際操作 ms 中的 cache
// CachingExecutor.java
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
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); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// 默認也為true
可以看到,只有當 cache 不為 null 才會實際操作緩存,在解析 mapper.xml 的時候才會判斷是否設置了 cache 類型
// XMLMapperBuilder.java
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);
}
}
// 類型有很多種
此文由 Mix Space 同步更新至 xLog 原始鏈接為 https://me.liuyaowen.club/posts/default/Mybatisplus