Overview of the Calling Process:#
MyBatis originated from iBATIS, where the native iBATIS executes CRUD operations as fixed operations, using methods in the SQLSession interface for adding, deleting, querying, and modifying. We first read the code for the iBATIS part, and later MyBatis [plus] expands on this foundation with MapperProxy and encapsulation of dynamic SQL conditions for preset SQL statements. Let's take a look at the core interface of iBATIS's 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);
}
The four types of methods ultimately required in this interface are (selectList, selectMap, selectCursor), insert, delete, and update.
However, the actual execution of SQL statements is done by methods in the 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;
}
These are divided into query and update methods, ultimately executed by the actual subclass methods doQuery and doUpdate, which are further handled by the StatementHandler object.
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();
}
It can be seen that the final methods reached are update or query.
Thus, the calling logic is SqlSession->Executor->StatementHandler, where each SqlSession has an Executor, and each Executor has a StatementHandler (obtained from configuration, wrapped by plugins, which will be discussed later);
Creation process of SqlSession:
The core of this is Configuration, and the entire iBATIS revolves around this Configuration class, which is very important for creating Executor, ParameterHandler, ResultSetHandler, and saving MapperStatements.
1. First-Level and Second-Level Caches#
First-Level Cache
Exists within the executor and is carried with each creation, so there is a first-level cache in every session: 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;
}
Before querying, it first checks the cache, where the key value is related to four aspects.
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
Only when the SQL statement, parameters, pagination, and bound parameters of the SQL match will it be considered a match.
Second-Level Cache
Exists within the cache attribute of ms, and the cacheExecutor will only be created when cacheEnable in configuration is true.
// 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;
}
// Default is true
At the same time, ms's useCache must be true for the cache in ms to be actually operated on.
// 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);
}
// Default is also true
It can be seen that the cache will only be operated on when it is not null, and the cache type will be determined when parsing mapper.xml.
// 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);
}
}
// There are many types
This article is updated synchronously to xLog by Mix Space The original link is https://me.liuyaowen.club/posts/default/Mybatisplus