Overview of the Calling Process:#
MyBatis originated from iBATIS, and 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 by encapsulating MapperProxy and 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 SQL statements are executed 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's doQuery and doUpdate methods, 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();
}
You can see that it ultimately reaches the update or query methods.
Thus, the calling logic is SqlSession->Executor->StatementHandler, where each SqlSession has one Executor, and each Executor has one StatementHandler (obtained from the 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 Cache#
First-Level Cache
Exists within the executor, and each creation carries it, so there is always a first-level cache in each 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);
It will only match when the SQL statement, parameters, pagination, and bound parameters are the same.
Second-Level Cache
Exists within the cache attribute of ms, and will only create cacheExecutor when cacheEnable in the 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 to actually operate on ms's 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);
}
// Default is also true
As you can see, it will only operate on the cache when cache is not null, and the cache type is 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