A, introducing
In the previous article, we briefly analyzed the initialization code of Mybatis, understood the simple structure of the configuration class in MyBatis, and understood that in the Mapper file defined by us, one SQL tag is stored in the form of MappedStatement. The namespace of the Mapper file and THE ID of the SQL tag are used to form keys, and the MappedStatement is used to form values, which are stored in the Map in the Configuration. MappedStatement stores the information needed from an SQL execution to parsing the result set into the desired Java object. It is the Executor object in Mybatis that uses this information
Second, cache system analysis
Mybatis defines the Cache interface Cache, as follows: mybatis defines the Cache interface Cache, as follows: mybatis defines the Cache interface Cache, as follows:
public interface Cache {
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear(a);
int getSize(a);
}
Copy the code
The Cache interface is very clear: put Cache, get Cache, remove Cache, empty Cache, etc. The inheritance structure of Cache is shown in the following figure. There is only one layer structure, which defines a class and implements the function of the Cache interface
The PerpetualCache is a cache that uses a Map object. LruCache is a cache that uses the LRU algorithm, FifoCache is a fifo, fifO, fifo, fifo, fifo. The default cache object used in MyBatis is PerpetualCache. Other types of cache objects can be configured to PerpetualCache. Mybatis is a decorator. The decorator pattern has the ability to enhance objects, i.e. we can create a cache with multiple functions (such as LRU, FIFO). If you know anything about the Java stream API, you can also know that the InputStream implementation class is a typical decorator pattern, as follows:
BufferedInputStream inputStream = new BufferedInputStream( new FileInputStream( "/test"));Copy the code
BufferedInputStream is of type InputStream, FileInputStream is of type InputStream, BufferedInputStream takes an argument of type InputStream, To achieve buffered input stream, mybatis Cache module is the same
Third, source code analysis
3.1 Executor interface method analysis
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;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
Transaction getTransaction(a);
}
Copy the code
Mybatis (mybatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBatis, MyBAtis, MyBAtis, MyBAtis, MyBAtis, MyBAtis) BoundSql saves dynamic SQL (e.g. Select xx from xx where id = #{id}, Select xx from xx where id =? This is the result of parsing dynamic SQL during initialization. If you are interested in parsing dynamic SQL, you can further study the process of building MappedStatement during initialization of Mybatis. After that, the question mark in SQL will be filled with parameter. A cacheKey is a key-related object in the Executor’s caching capabilities. There are two types of caches, which we’ll cover later
Executor provides all of the database operations (including calls to stored procedures), basic add, delete, update, and rollback transactions. Therefore, Executor uses the information stored in MappedStatement to execute SQL and process result sets. Called the executor, we are going to analyze this part of the source code
3.2 Executor Interface Inheritance Hierarchy
BaseExecutor is a common implementation that performs all the common functions of SQL execution. SimpleExecutor is the most common Executor that encapsulates familiar JDBC code. Unlike SimpleExecutor, ReuseExecutor caches PreparedStatement objects created from SQL. BatchExecutor provides batch operations, and generally we use SimpleExecutor
On the other side, you can see the CachingExecutor, which is at the same level as BaseExecutor. This is a typical decorator pattern, and CachingExecutor provides another level of caching (we’ll look at that later, too, Caching is not the same as caching in the Executor interface, which specifies the caching function.) All database operations except caching are delegated to other executors, so CachingExecutor is similar to the following structure:
public class CachingExecutor { Executor delegate; Void query() {if (cache exists) {return cache} else {Object result = delegate.query(); Write result to cache}}}Copy the code
3.3. SimpleExecutor source code analysis
3.3.1. Query method analysis
After understanding the Executor inheritance hierarchy, we start to analyze the source code of the SimpleExecutor interface, using the BaseExecutor Query method as the entrance to analyze the common part first, BaseExecutor defines the template, SimpleExecutor does what the template requires subclasses to do:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) {
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--;
}
return list;
}
Copy the code
BaseExecutor provides the caching function as defined by Executor. Let’s look at the first query method, which calls the createCacheKey method using SQL and SQL parameters, builds the cache key according to certain rules, and then calls the second method. So we can also customize the rules for cache keys
In the second method, the first if judgment states that if cache is not used (forcing cache refresh), then clearLocalCache is called to clear the cache. QueryStack is the number of query nesting times. We allow nested queries when implementing result set mapping, i.e. defining resultMap. QueryStack refers to the number of levels of nested queries, and each nested query results in queryStack ++, Therefore, when queryStack is 0, mapper is executed at the beginning (students unfamiliar with nested query can follow the example of Mybatis Chinese official website to write a nested query, which is rarely used in my current contact).
The implementation of localCache is PerpetualCache, so in this hierarchy of caches, you’re using a Map to perform a cache. If you get a list that’s not empty, it’s a cache. Performed handleLocallyCachedOutputParameters method at this time, otherwise call queryFromDatabase method from the database query data, HandleLocallyCachedOutputParameters method we don’t have to focus on the associated with a stored procedure, it will be according to the type of MappedStatement, if it is stored procedure is executed a certain logic, otherwise what also does not perform
Cache this piece we will take a special article to explain, this requires us to deeply understand the SqlSession component to better master mybatis two-layer cache is how to operate
3.3.2 queryFromDatabase method analysis
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
return list;
}
Copy the code
Before we actually query the database, we put a placeholder object in the cache. When we query the data from the database, we remove the placeholder object to put the real cache object. This operation depends on the level of the cache. SqlSession = sqlSession localCache = sqlSession localCache = sqlSession localCache = sqlSession localCache = sqlSession localCache = sqlSession localCache = sqlSession localCache = sqlSession localCache If more than one person is using a session at the same time (that is, multi-threaded SQLSession), you need to throw an exception, that is, type conversion exception….. An exception is used to tell the cache that an operation is illegal, a bit like a concurrent modification exception raised when a collection is traversed.)
3.3.3 StatementHandler system analysis
Before we look at doQuery, let’s talk about the StatementHandler component
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout);
void parameterize(Statement statement);
void batch(Statement statement);
int update(Statement statement);
<E> List<E> query(Statement statement, ResultHandler resultHandler);
<E> Cursor<E> queryCursor(Statement statement);
BoundSql getBoundSql(a);
ParameterHandler getParameterHandler(a);
}
Copy the code
Before we execute doQuery, we have the MappedStatement object, which holds the information needed to execute an SQL, and the SQL parameter, which replaces the question mark in SQL. Recall that when we started learning JDBC programming, with these, We create a Statement object, set the parameters of each question mark, and then call its corresponding SQL method. StatemetHandler uses the existing data to perform these JDBC operations
-
prepare: This method is used to create a Statement. You can create a PreparedStatement by pre-compiling it (i.e. replacing the question mark with SQL parameters to prevent SQL injection), or you can create a CallableStatement that executes a stored procedure. You can also create the most common statements (no SQL injection prevention)
-
Parameterize: When parameterdstatement is PreparedStatement, the corresponding setxxx method is called. When PreparedStatement is PreparedStatement, no operation is performed
-
Batch, UPDATE, Query, and queryCursor perform the corresponding SQL functions
-
ParameterHandler: The default implementation class does only one function: select the corresponding TypeHandler using the Java type, and then set the SQL parameter. In fact, it completes the setXXX method PreparedStatement in JDBC programming
StatementHandler is used to create a JDBC Statement using the prepare method. Parameterize is used to map SQL parameters. Parameterize is used to map SQL parameters by calling setXXX methods in PreparedStatement. Parameterize is used to map SQL parameters using ParameterHandler. The default ParameterHandler is to select the corresponding TypeHandler using the Java type and then execute a different setXXX method because there is only one ParameterHandler. And the code is very simple, so I won’t expand it here, if you’re interested, you can look at it (using TypeHandler).
After analyzing the functions of the interface, we can see the inheritance system more clearly, as shown in the figure below. BaseStatementHandler implements the operations mentioned above, because these operations are universal. If it is PreparedStatement, it will be the complete process described above. If it is a plain Statement, the parameterize method is an empty implementation, and so on
PreparedStatementHandler is used to complete SQL functions in PreparedStatement. SimpleStatementHandler is used to create the most common Statement to complete SQL execution. CallableStatementHandler is a call to a stored procedure, a very clear implementation class of three, and these handlers in addition to SQL execution also handle result sets, as we’ll see more clearly later
RoutingStatementHandler is at the same level as BaseStatementHandler. It does the routing function, creates the three handlers we described above, and executes the logic, based on the current SQL type. So mybatis creates a RoutingStatementHandler when it creates StatementHandler, and then actually creates a different StatementHandler for different SQL types
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break; }}@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
returndelegate.prepare(connection, transactionTimeout); }... }Copy the code
So the real functional operation, the RoutingStatementHandler, does nothing but delegate objects
3.3.4 SimpleExecutor doQuery method source code analysis
Back to our doQuery method:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
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); }}Copy the code
As you can see, mybatis can obtain the top-level Configuration object Configuration from MappedStatement, and then create StatementHandler. What you’re actually creating here is a RoutingStatementHandler
private Statement prepareStatement(StatementHandler handler, Log statementLog) {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
Copy the code
Prepare (StatementHandler); prepare (StatementHandler); prepare (StatementHandler);
PreparedStatement statement = connection.prepareStatement(sql); The parameters are then set using methods such as Statement.setString (XXX) called by TypeHandlerCopy the code
DoQuery finally calls the query method of StatementHandler, and we PreparedStatementHandler for analysis
public <E> List<E> query(Statement statement, ResultHandler resultHandler) {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
Copy the code
The ResultSet is mapped to Java objects. The ResultSet is mapped to Java objects
3.3.5 getConnection method analysis
protected Connection getConnection(Log statementLog) { Connection connection = transaction.getConnection(); } A transaction is used to obtain a connection.public interface Transaction {
Connection getConnection(a) throws SQLException;
void commit(a) throws SQLException;
void rollback(a) throws SQLException;
void close(a) throws SQLException;
Integer getTimeout(a) throws SQLException;
}
Copy the code
The Transaction Transaction interface provides the functions to acquire a connection, commit a Transaction, roll back a Transaction, close a connection, and so on. As you can see, here we already have a Transaction object that was created when the SQLSession was created. In this section, Just know that we can get the corresponding database connection!! How can this connection be obtained directly from here? Let’s go to the SQLSession section
4, summarize
An Executor interface is an Executor that performs JDBC operations and returns values based on MappedStatement(which stores SQL and result mappings) and parameters. Executor defines a layer of caching at the interface level. This layer of caching is implemented by default using a Map, and executors need to schedule certain methods to complete JDBC operations
BaseExecutor does the functionality common to all subclasses (caching, nested queries, and so on). SimpleExecutor is the one we’re really going to use. Using query as an example, it does this in the doQuery method to get StatementHandler, Get the RoutingStatementHandler, and then use the RoutingStatementHandler to create the corresponding StatementHandler according to the type of SQL and delegate it to complete the corresponding query function. PreparedStatementHandle is normally commissioned. Prepare creates Statement, then maps parameters, and finally calls the execute method to complete the query. Finally, the result set is mapped
So far, we have analyzed the entire SQL execution process, which has skipped over how result sets are mapped and how parameters are set using TypeHandler. If you are interested in these things, you can delve into them more easily once you have a sense of the whole thing. Again, just to remind you, GetConnection method is the core function of Spring integration of mybatis to synchronize data sources. We will see how to synchronize data sources when we analyze relevant codes later