In the previous article we looked at the three types of Executor executors, and each of the Update and Query methods has a Statement and a StatementHandler operation. In this article we will cover the relationship between Statement and StatementHandler, as well as their implementation details.
series
MyBatis principle series (a)- hand handle with you to read MyBatis source code MyBatis principle series (two)- hand handle with you to understand MyBatis startup process MyBatis principle series (3) – take you understand the SqlSession, holding SqlSessionFactory, principle of the relationship between the SqlSessionFactoryBuilder is MyBatis series (4) – handy belt you know MyBatis Executor of the actuator MyBatis principles series (6)- Learn how to create a BoundSql Statement, StatementHandler, and mappedsql MyBatis principle series (9)- MyBatis principle series (9)- MyBatis with your understanding of the transaction management mechanism
1. The Statement object
In the original JDBC operation, you load the driver, set properties, obtain connections, and create a Statement object… And so on. In JDBC operations, the Statement object sends an SQL Statement to the database and retrieves the execution result. There are three types of Statement objects: Statement, PreparedStatement, and CallableStatement. Their inheritance is as follows
Statement
: Can send SQL as a string, but does not support passing parameters. This applies to static SQL statements.PreparedStatement
: a precompiled SQL statement that accepts arguments and prevents SQL injection to improve security. Sql statements are compiled in the database system and can be used to execute the same Sql multiple times. Therefore, they perform better than statements.CallableStatement
: used when executing stored procedures, and can accept arguments passed in.
The Statement interface has many methods, which are used to perform updates, queries, and fetch results.
2. StatementHandler object
2.1 StatementHandler Object Acquaintance
StatementHandler objects are literally managing Statement objects. It has two direct implementations, BaseStatementHandler and RoutingStatementHandler. And then BaseStatementHandler has three implementations: SimpleStatementHandler, PreparedStatementHandler, CallableStatementHandler, They manage the Statement, PreparedStatement, and CallableStatement objects described above. BaseStatementHandler uses the adapter pattern to reduce the complexity of implementing interfaces, while RoutingStatementHandler wraps all three types of handlers. Operates as a proxy class.
The methods of the StatementHandler interface are as follows
/ * * *@author Clinton Begin
*/
public interface StatementHandler {
// Create a Statement object
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
// Assign placeholders in Sql
void parameterize(Statement statement)
throws SQLException;
// Add to the batch operation
void batch(Statement statement)
throws SQLException;
// Perform the update operation
int update(Statement statement)
throws SQLException;
// Perform the query and return the result
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
// Get the BoundSql object
BoundSql getBoundSql(a);
// Get the parameter handler
ParameterHandler getParameterHandler(a);
}
Copy the code
2.2 Creating a StatementHandler Object
Where is StatementHandler created? In the Executor Executor of MyBatis, the StatementHandler object is created when the doQuery and doUpdate methods are executed. In the case of SimpleExecutor, the StatementHandler object is actually created by the Configuration object.
// SimpleExecutor doUpdate method
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
1. Create StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null.null);
// 2. Create Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 3. Perform the SQL operation
return handler.update(stmt);
} finally {
// 2. Close StatementcloseStatement(stmt); }}Copy the code
In the newStatementHandler method of Configuration, the RoutingStatementHandler object is created. We know that the RoutingStatementHandler is actually a wrapper around the three Statementhandlers.
// The newStatementHandler method of Configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// Load the plug-in
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
Copy the code
Click to create a specific StatementHandler object based on the type of the MappedStatement object. If MappedStatement does not specify a specific StatementType, StatementType defaults to PREPARED.
// The actual processor, one of the three processors: SimpleStatementHandler, PreparedStatementHandler, CallableStatementHandler
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;
default:
throw new ExecutorException("Unknown statement type: "+ ms.getStatementType()); }}Copy the code
Next, let’s look at the PreparedStatementHandler creation process, which actually calls the BaseStatementHandler constructor.
// PreparedStatementHandler constructor
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// The BaseStatementHandler constructor is called
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
Copy the code
At this point, we know that all three Statementhandlers are created using the BaseStatementHandler constructor.
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
Copy the code
2.3 Creating a Statement Object
Once the StatementHandler object is created, you can create the Statement object. Again, take the SimpleExecutor executor as an example.
1. Create StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null.null);
// 2. Create Statementstmt = prepareStatement(handler, ms.getStatementLog()); .Copy the code
SimpleExecutor’s prepareStatement method does the following three steps:
- Getting a database connection
- Creating a Statement object
- Setting SQL Parameters
// The prepareStatement method of SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 1. Obtain the database connection
Connection connection = getConnection(statementLog);
2. Create a Statement object
stmt = handler.prepare(connection, transaction.getTimeout());
// 3. Set SQL parameters
handler.parameterize(stmt);
return stmt;
}
Copy the code
Moving on, let’s look at what the prepare() method does. This method BaseStatementHandler gives the default implementation, so all three Statementhandlers use this implementation. The main work is as follows
- Initialize the Statement object
- Setting timeout
- Set query size
- The Statement object is closed abnormally
// BaseStatementHandler prepares
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 1. Initialize the Statement object
statement = instantiateStatement(connection);
// 2. Set the timeout period
setStatementTimeout(statement, transactionTimeout);
// 3. Set the query size
setFetchSize(statement);
return statement;
} catch (SQLException e) {
// 4. Close the Statement object
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: "+ e, e); }}Copy the code
The Statement object initialization is done in the instantiateStatement method, so let’s look at what the instantiateStatement method does. Ok, the instantiateStatement method in BaseStatementHandler is designed as an abstract method and implemented by subclasses, which also reflects the template method design pattern.
// instantiateStatement method of BaseStatementHandler
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
Copy the code
Now SimpleStatementHandler as example, the final call or connection. The createStatement () method, returned to the original starting point, namely MyBatis JDBC operations for the packaging.
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
/ / is actually the call connection. The createStatement () method
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.createStatement();
} else {
returnconnection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); }}Copy the code
Once you get the Statement object, you can happily execute the execute method to send the SQL Statement to the database.
3. MappedStatement object
For those of you who are discerning, you will see that when Executor executes a doUpdate, it passes a MappedStatement object. What is the relationship between the MappedStatement object and the Statement and StatementHandler object? When we use MyBatis configuration in SQL, insert/update/delete/select the label below contains a SQL, MappedStatement is information description of SQL TAB.
// A select tag corresponds to an MappedStatement object<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_test_user
where id = #{id,jdbcType=BIGINT}
</select>
Copy the code
The MappedStatement class has the following private properties
// mapper Configuration file name, for example, usermapper. XML private String resource; // Private Configuration Configuration; / / namespace + id tags such as com. Example. Demo. Dao. TTestUserMapper. SelectByPrimaryKey private String id; private Integer fetchSize; Private Integer timeout; // SQL object type STATEMENT, PREPARED, CALLABLE private StatementType StatementType; // ResultSetType private ResultSetType ResultSetType; // SQL statement private SqlSource SqlSource; Private Cache Cache; Parameter mapping private ParameterMap ParameterMap; Private List<ResultMap> resultMaps; private List<ResultMap> resultMaps; private boolean flushCacheRequired; Private Boolean useCache; Private Boolean resultOrdered; // SQL statement type, INSERT, UPDATE, DELETE, SELECT private SqlCommandType SqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets;Copy the code
The main method is the getBoundSql method, which parses the dynamic SQL to get the final SQL statement. We will cover SqlSource and BoundSql in other articles.
public BoundSql getBoundSql(Object parameterObject) {
// Get the BoundSql object, which is a parse of dynamic SQL
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// Get the parameter mapping
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if(rmId ! =null) {
ResultMap rm = configuration.getResultMap(rmId);
if(rm ! =null) { hasNestedResultMaps |= rm.hasNestedResultMaps(); }}}return boundSql;
}
Copy the code
4. To summarize
This article introduces the Statement object, StatementHandler and its three implementations, as well as the MappedStatement. As the main component of the Statement object, understanding its creation and principle will be very helpful for us to understand MyBatis.