Public search “code road mark”, point of concern not lost!
The last article “Mybatis source CODE SqlSession” talked about THE SqlSession is actually a contractor head, pull their own work is not arranged to the executer; And in “Mybatis source SQL execution process” has learned that the executer through the StatementHandler life cycle scheduling and management, the final completion of SQL command execution.
Today we will take a look at one of the four components of Mybatis: Executor. To be clear, since Executor is involved in Mybatis level 1 cache and Level 2 cache, this section will be discussed separately in this article.
Executor profile
Executor resides in the Executor package, which schedules the execution of all SQL commands in Mybatis. First, take a look at the Executor family through a UML class diagram:
- At the top level is the Executor interface, which defines interface methods related to query, update, transaction, and cache operations. The Executor interface is exposed, dependent on, scheduled, and managed by SqlSession.
- The second layer on the left is BaseExecutor, which is an abstract class that implements most Executor interfaces. It has three subclasses, namely SimpleExecutor, ReuseExecutor, and BatchExecutor. BaseExecutor and its subclasses perform level 1 cache management and operations related to database interaction.
- The second layer on the right is CachingExecutor, the cache executor, the core processing class of Mybatis level 2 cache. CachingExecutor holds an instance of a BaseExecutor implementation class (SimpleExecutor, ReuseExecutor, or BatchExecutor) as the delegate executor. It mainly completes the Mybatis level 2 cache processing logic, when the cache query does not exist or cannot query the result, it will query the database through the delegate executor.
- The third layer is the three subclasses of BaseExecutor. The simple actuator is the default actuator and has all the capabilities of the actuator. A reusable executor is a simple executor. It has the caching and reuse capabilities of MappedStatement. That is, if the same command is executed repeatedly in a SqlSession, the cached MappedStatement can be directly reused. Batch executor, that is, multiple commands can be executed at a time.
Executor’s core function is to schedule the execution of SQL. To improve query performance, Mybatis designs level 1 cache and level 2 cache in Executor. Level 1 cache is implemented by BaseExecutor and its subclasses, and level 2 cache is implemented by CachingExecutor. Level-1 cache is enabled by default. Level-2 cache must be enabled. Because CachingExecutor is responsible for cache management, and the real database queries are done by BaseExecutor, there are three Executor types externally: SIMPLE, REUSE, and BATCH. The default is SIMPLE. We can change the default executor type by specifying parameters in the configuration file or when creating the SqlSession. The following is the definition of ExecutorType in Mybatis:
public enum ExecutorType {
// Simple actuator
SIMPLE,
// A reusable actuator
REUSE,
// Batch executor
BATCH
}
Copy the code
Creation Process Analysis
As mentioned earlier, SqlSession relies on Executor, which accepts SqlSession requests and executes them, and Executor creation is created with SqlSession creation. The Executor creation process starts with DefaultSqlSessionFactory#openSession() and its overloaded methods:
@Override
public SqlSession openSession(a) {
// Use the default actuator type: SIMPLE
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
/ / by configuration. NewExecutor method to create executor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally{ ErrorContext.instance().reset(); }}Copy the code
Simple analysis about this code: here by countless openSession method, its internal use Configuration# defaultExecutorType as actuator types call openSessionFromDataSource method; The latter calls Configuration#newExecutor(), which does nothing for execType, to execute the Executor creation process. Configuration#defaultExecutorType defaults to SIMPLE, which will be modified when Mybatis parses the configuration file. If the configuration file does not involve executorType configuration, the default value does not change. Alternatively, an overloaded method of openSession can be called to specify the executor type. The two Settings are as follows:
- The default actuator type of Mybatis is SIMPLE, which can be modified in the configuration file:
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
Copy the code
- Or set it in code:
REUSE specifies the REUSE actuator type
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)) {
/ /...
}
Copy the code
Knowing how to set up ExecutorType, look at the Configuration#newExecutor() method:
//Executor creates an Executor method
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//executorType is null: use the default executor; otherwise, use the input type
ExecutorType defaultExecutorType = executortype. SIMPLE;
executorType = executorType == null ? defaultExecutorType : executorType;
//executorType and defaultExecutorType are both null, and SIMPLE is used by default
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// Create different executorTypes according to executorType.
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);
}
// The default value is true.
// If the configuration file is not modified, the if statement will be executed
if (cacheEnabled) {
// Create a cache executor with the executor created above as its delegate executor
executor = new CachingExecutor(executor);
}
// Load Executor related plugins.
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
Copy the code
To summarize the implementation process:
- First: process the actuator type parameter to ensure that it is not empty, and finally to SIMPLE bottom;
- Second, call the corresponding executor implementation class to initialize the executor according to the executor type. The executor can be SimpleExecutor, ReuseExecutor, or BatchExecutor.
- Then: If level 2 caching is enabled (cacheEnabled defaults to True, but it does not work if you do not set cache parameters), create a cache executor and use the executor created above as a delegate executor for the cache executor.
- Finally: Load executor-related plug-ins.
By default, a CachingExecutor object is returned with some subclass of BaseExecutor wrapped inside.
The query process
The Executor interface has two overloads of the Query method. We start with the one with fewer arguments, and it calls the other one internally. CachingExecutor is used by default, so we’ll start with CachingExecutor#query() :
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
/ / get BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
// Create a cache key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// Call the overloaded query method
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// Get the cache object
Cache cache = ms.getCache();
// The cache is not empty
if(cache ! =null) {
// Flush the cache
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// Query from the cache
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// The cache does not have any
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
returnlist; }}// Level 2 cache is not enabled by default
Delegate is one of the three subclasses of BaseExecutor
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
Copy the code
Enter CachingExecutor# Query, first fetch BoundSql through MappedStatement, create the cache key, and then call the overloaded Query method. Overloaded Query queries are queried directly through the delegate executor’s Query method without regard to caching. The delegate executor is a subclass of BaseExecutor, which implements the Query method, so we’ll start with BaseExecutor#query() (again ignoring the logic of the level-i cache) :
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++;
// Query from level 1 cache (local cache)
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if(list ! =null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// Do not exist in cache, query databaselist = 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 #482clearLocalCache(); }}return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// Cache placeholder
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// Call abstract methods to perform database queries, subclass implementation
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// Remove the placeholder
localCache.removeObject(key);
}
// Set the cache
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
Copy the code
The two methods are longer and are mostly cached. Ignoring the cache (see my comments directly), the database query method queryFromDatabase is called from the Query method, but the real query logic is implemented in the abstract method doQuery, which is implemented by a BaseExecutor subclass. Let’s look at the subclass implementation logic in turn:
SimpleExecutor#doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// Get the configuration object
Configuration configuration = ms.getConfiguration();
/ / create StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
/ / for the Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// Execute the query
return handler.query(stmt, resultHandler);
} finally {
/ / close the StatementcloseStatement(stmt); }}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;
}
Copy the code
The code for the SimpleExecutor#doQuery method is relatively simple, and the process is straightforward. Here’s a brief description:
- Get the global Configuration object from the MappedStatement object.
- Call Configuration#newStatementHandler to create the StatementHandler object.
- Create and initialize the Statement object.
- Call StatementHandler#query to execute the Statement and use resultHandler to parse the return value.
- Finally, close the Statement.
DoQuery scheduling StatementHandler completes the initialization, parameter setting, execution, result processing, and closing of a Statement, and manages and controls the entire life cycle of the Statement. As mentioned above, executors are involved in the entire execution of SQL statements.
ReuseExecutor#doQuery
/ / the Statement cache
private final Map<String, Statement> statementMap = new HashMap<>();
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// Check whether the current SQL exists in the cache
if (hasStatementFor(sql)) {
// If you have one, use it directly
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
// If not, create a new one
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
// Then cache it.
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
Copy the code
The logic of ReuseExecutor#doQuery is basically the same as that of SimpleExecutor#doQuery, except for the implementation logic of the prepareStatement method. PrepareStatement uses statementMap to cache executed SQL. The creation process is executed only when statementMap does not contain the current SQL, which improves performance. Note that Executor objects are part of the SqlSession, so this cache is consistent with the LIFECYCLE of the SqlSession.
BatchExecutor#doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
// Execute the statement in batches
flushStatements();
// Obtain the Configuration object
Configuration configuration = ms.getConfiguration();
/ / create StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
/ / create the Statement
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
// Set the Statement parameter
handler.parameterize(stmt);
// Execute and return the result
return handler.query(stmt, resultHandler);
} finally{ closeStatement(stmt); }}Copy the code
The BatchExecutor#doQuery method performs more flushStatements than SimpleExecutor and no longer expands.
The update () process
The update() method corresponds to insert, update, delete and other commands, and its execution flow is similar to that of the query() method. Is also a CachingExecutor – > BaseExecutor – > SimpleExecutor/ReuseExecutor/BatchExecutor. Post the entire process code together for analysis (comment) :
//org.apache.ibatis.executor.CachingExecutor#update
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// Refresh cache: When caching is enabled, the update command refreshes the cache by default
flushCacheIfRequired(ms);
// Call the delegate executor to update
return delegate.update(ms, parameterObject);
}
//org.apache.ibatis.executor.BaseExecutor#update
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// Clear the cache and call the subclass's doUpdate method
clearLocalCache();
// Call the abstract method to perform the database update operation
return doUpdate(ms, parameter);
}
Copy the code
Mybatis cache only works for queries, so all Executor can do is invalidate the cache, relay the request, and finally call doUpdate to perform the database operation, so:
- CachingExecutor#update first invalidates the secondary cache, and then invokes the delegate executor to perform the update operation.
- BaseExecutor#update also invalidates the level-1 cache first, and then calls the abstract method doUpdate to perform database updates.
SimpleExecutor#doUpdate
SimpleExecutor#doUpdate is exactly the same as doQuery, no further explanation.
ReuseExecutor#doUpdate
ReuseExecutor#doUpdate is exactly the same as doQuery, no further clarification.
BatchExecutor#doUpdate
The BatchExecutor execution is different from the first two in that it is used to execute batch SQL commands, so there is a bit more batch preparation. To reduce the number of interactions with the database, BatchExecutor executes SQL commands in batches. The code is as follows:
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
// Obtain the Configuration object
final Configuration configuration = ms.getConfiguration();
/ / create StatementHandler
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null.null);
// Get the SQL object
final BoundSql boundSql = handler.getBoundSql();
// Get SQL and wine
final String sql = boundSql.getSql();
final Statement stmt;
// If the current command is the same as the last one, the Statement is not created again and the performance is improved
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// Retrieve the index of the last item
int last = statementList.size() - 1;
// Retrieve the last Statement object
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
// Set the Statement parameter
handler.parameterize(stmt);//fix Issues 322
/ / get BatchResult
BatchResult batchResult = batchResultList.get(last);
// Set the BatchResult parameter object
batchResult.addParameterObject(parameterObject);
} else {
// If the current command is different from the last one, create it again
Connection connection = getConnection(ms.getStatementLog());
// Initialize Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// Set parameters
handler.parameterize(stmt); //fix Issues 322
// Set the current SQL command information
currentSql = sql;
currentStatement = ms;
/ / save it
statementList.add(stmt);
// Save the result object
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// Batch processing
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
Copy the code
The BatchExecutor#doUpdate method prepares the Statement before executing it. When preparing the Statement, compare it with the Statement to be executed last time. If the Statement is the same, the process of recreating the Statement is not performed. Therefore, you should try to execute the same SQL commands when using BatchExecutor.
However, BatchExecutor#doUpdate does not perform database execution, it needs to be triggered by sqlssession #flushStatements, and then called to BatchExecutor#doFlushStatements to perform the final operation, I’m not going to expand it here.
Executor summary
Executor’s update/query execution process is described in detail in this article. The Executor’s update/query execution process is described in detail in this article. The Executor’s update/query execution process is described in detail in this article. Executor caching is something we haven’t expanded. Executor is one of the four components of Mybatis. Although we have not studied the other three components, we have already had an overall understanding of Mybatis SQL execution process, which shows that Executor plays a very important role.
That’s the end of this article, I hope it was useful to you! My level is limited, if you find any mistakes or improper place, welcome to criticize and correct.
Public search “code road mark”, point of concern not lost!