Mybatis MapperProxy initialization of the Mapper object scan and build 2, source analysis of Mybatis MappedStatement creation process 3, Mybatis SQL execution of 4 basic components detailed explanation
MyBatis Sharding-Jdbc SQL statement execution process detailed explanation
-
- 1, SQL execution sequence diagram
- 2, source code analysis SQL execution process
-
- 2.1 MapperProxy# invoker
- 2.2 MapperMethod# execute
- 2.3 MapperMethod# executeForMany
- 2.4 DefaultSqlSession# selectList
- 2.5 BaseExecutor# query
- 2.6 SimpleExecutor# doQuery
- 3. Statement object creation process
-
- 3.1 Java.sql.Connection Object Creation
-
- 3.1.1 SimpleExecutor# prepareStatement
- 3.1.2 SimpleExecutor# getConnection
- 3.2 Creating a Java.sql.Statement Object
-
- 3.2.1 BaseStatementHandler# prepare
- 3.2.2 PreparedStatementHandler# instantiateStatement
- 3.2.2 ShardingConnection# prepareStatement
- 4. SQL execution process
-
- 4.1 PreparedStatementHandler# query
- 4.2 ShardingPreparedStatement# execute
- 4.3 ShardingPreparedStatement# routeSQL
- 4.4 PreparedStatementExecutor# execute
- 4.4 DefaultResultSetHandler# handleResultSets
Mybatis Mapper class initialization process, because in Mybatis, Mapper is the entrance to execute SQL statements. Something like the following code:
@Service
public UserService implements IUserService {
@Autowired
private UserMapper userMapper;
public User findUser(Integer id) {
returnuserMapper.find(id); }}Copy the code
Start to enter the topic of this article, to source code as a means to analyze the popularity of Mybatis SQL statement, and the use of database sub-database sub-table middleware Sharding-JDBC, its version is Sharding-JDBC1.4.1.
In order to facilitate the source analysis of this article, the method call sequence diagram of the core class of Mybatis layer is given first.
1, SQL execution sequence diagram
2, source code analysis SQL execution process
Next from the source point of view to analyze it.
Tips: At the end of this article, a detailed Mybatis Shardingjdbc statement execution flow chart is provided. (Don’t miss it.)
2.1 MapperProxy# invoker
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throwExceptionUtil.unwrapThrowable(t); }}final MapperMethod mapperMethod = cachedMapperMethod(method); / / @ 1
return mapperMethod.execute(sqlSession, args); / / @ 2
}
Copy the code
Code @1: Create and cache the MapperMethod object.
@2: Call the execute method of the MapperMethod object, that is, each method defined in the mapperInterface will eventually correspond to a MapperMethod.
2.2 MapperMethod# execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else{ Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); }}else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null&& method.getReturnType().isPrimitive() && ! method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
Copy the code
Execute execute many (SQL Session, ARGS); execute execute many (SQL Session, ARGS);
2.3 MapperMethod# executeForMany
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if(! method.getReturnType().isAssignableFrom(result.getClass())) {if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
returnconvertToDeclaredCollection(sqlSession.getConfiguration(), result); }}return result;
}
Copy the code
This method is also relatively simple, and ultimately calls the selectList method through SqlSession.
2.4 DefaultSqlSession# selectList
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement); / / @ 1
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); / / @ 2
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally{ ErrorContext.instance().reset(); }}Copy the code
Code @ 1: according to the resource name to obtain the corresponding MappedStatement object, at this time of the statement for the resource name, such as com. Demo. UserMapper. FindUser. The generation of the MappedStatement object was described in detail during initialization in the previous section and will not be repeated here.
Code @2: Calls Executor’s query method. To clarify, you’ll start with the CachingExecutor# Query method, and since the Executor delegate attribute of CachingExecutor defaults to SimpleExecutor, So you’ll end up in SimpleExecutor#query.
Next we go to the Query method of BaseExecutor, the parent of SimpleExecutor.
2.5 BaseExecutor# query
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { / / @ 1
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; / / @ 2
if(list ! =null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); / / @ 3}}finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { / / @ 4
clearLocalCache(); // issue #482}}return list;
}
Copy the code
@1: First, let’s introduce the input parameters of this method. These classes are important classes of Mybatis:
- MappedStatement An MappedStatemnet object that represents a method in a Mapper is the most basic object for mapping.
- Object Parameter Parameter list of SQL statements.
- RowBounds The RowBounds object is actually the paging parameters limit and size.
- ResultHandler ResultHandler Results process Handler.
- CacheKey key Mybatis CacheKey
- BoundSql BoundSql BoundSql BoundSql Binds information about the SQL statements in the mapping file.
Mybatis supports level 1 cache (SqlSession) and Level 2 cache (multiple SQLsessions shared).
Code @3: Query the results from the database, and then enter the doQuery method to perform the actual query action.
Code @4: If the level 1 cache is statement level, the cache is deleted after the statement execution is complete.
2.6 SimpleExecutor# doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); / / @ 1
stmt = prepareStatement(handler, ms.getStatementLog()); / / @ 2
return handler.<E>query(stmt, resultHandler); / / @ 3
} finally{ closeStatement(stmt); }}Copy the code
@1: Create StatementHandler with Mybatis plug-in extension mechanism (described in detail in the next article), as shown in the figure below:
@2: Create a Statement object. Note that this is the java.sql.Statement object of JDBC protocol.
Code @3: Execute SQL statement using Statment object.
Next, the creation and execution of the Statement object are described in detail, namely, the distribution detail trace codes @2 and @3.
3. Statement object creation process
3.1 Java.sql.Connection Object Creation
3.1.1 SimpleExecutor# prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog); / / @ 1
stmt = handler.prepare(connection); / / @ 2
handler.parameterize(stmt); / / @ 3
return stmt;
}
Copy the code
Create a Statement object in three steps: @1: Create a java.sql.Connection object.
Code @2: Create a Statment object using a Connection object.
Code @3: Performs additional processing for the Statement, especially ParameterHandler for the PrepareStatement.
3.1.2 SimpleExecutor# getConnection
GetConnection method, according to the flow diagram shown above, first into the org. Mybatis. Spring. Transaction. SpringManagedTransaction, again through the spring – JDBC framework, Use DataSourceUtils to get the connection with the following code:
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if(conHolder ! =null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if(! conHolder.hasConnection()) { conHolder.setConnection(dataSource.getConnection()); }return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection(); / / @ 1
// Omit transaction-related code here
return con;
}
Copy the code
@1: A connection is obtained via a DataSource. Take a look at the configuration of our project:
Therefore, the SpringShardingDataSource obtains the connection from datasouce. getConnection.
com.dangdang.ddframe.rdb.sharding.jdbc.ShardingDataSource#getConnection
public ShardingConnection getConnection(a) throws SQLException {
MetricsContext.init(shardingProperties);
return new ShardingConnection(shardingContext);
}
Copy the code
The result is as follows:
Note: Only a ShardingConnection object is returned that contains the repository and table context, but no specific repository operation (switching data sources) is performed at this time.
Now that the Connection acquisition process is clear, let’s move on to the creation of the Statemnet object.
3.2 Creating a Java.sql.Statement Object
stmt = prepareStatement(handler, ms.getStatementLog());
Copy the code
Call chain for the above statement: RoutingStatementHandler – BaseStatementHandler
3.2.1 BaseStatementHandler# prepare
public Statement prepare(Connection connection) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection); / / @ 1
setStatementTimeout(statement); / / @ 2
setFetchSize(statement); / / @ 3
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: "+ e, e); }}Copy the code
Code @1: Creates a Statement object based on a Connection object (in this case ShardingConnection). Its default implementation class is the PreparedStatementHandler#instantiateStatement method.
Code @2: Sets the timeout for Statement.
Code @3: Set fetchSize.
3.2.2 PreparedStatementHandler# instantiateStatement
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
returnconnection.prepareStatement(sql, keyColumnNames); }}else if(mappedStatement.getResultSetType() ! =null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
returnconnection.prepareStatement(sql); }}Copy the code
If a Connection is a ShardingConnection, look at the prepareStatement method.
3.2.2 ShardingConnection# prepareStatement
public PreparedStatement prepareStatement(final String sql) throws SQLException { // SQL, the SQL statement configured in the mybatis XML file
return new ShardingPreparedStatement(this, sql);
}
ShardingPreparedStatement(final ShardingConnection shardingConnection,
final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) {
super(shardingConnection, resultSetType, resultSetConcurrency, resultSetHoldability);
preparedSQLRouter = shardingConnection.getShardingContext().getSqlRouteEngine().prepareSQL(sql);
}
Copy the code
In constructing ShardingPreparedStatement object, according to the SQL statement create parse SQL routing parser object, but this does not perform the related routing calculation, a PreparedStatement object creation is completed, began to enter the SQL execution process.
4. SQL execution process
Next we move on to step 3 of the SimpleExecutor#doQuery method, which executes the SQL statement:
Handler. < E > query (STMT, resultHandler).Copy the code
The RoutingStatementHandler class is used to route the Mybatis layer (based on the Statement type).
And then it goes to PreparedStatementHandler#query.
4.1 PreparedStatementHandler# query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute(); / / @ 1
return resultSetHandler.<E> handleResultSets(ps); / / @ 2
}
Copy the code
PreparedStatement code @ 1: call the execute method, because in this case is to use Sharding – JDBC depots table, at this time the realization of a call for: ShardingPreparedStatement.
Code @2: Process the result.
Let’s follow up the execute and result processing methods respectively.
4.2 ShardingPreparedStatement# execute
public boolean execute(a) throws SQLException {
try {
return new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), routeSQL()).execute(); / / @ 1
} finally{ clearRouteContext(); }}Copy the code
Here infinite mystery, its key points are as follows: 1) create PreparedStatementExecutor object, its two key parameters:
- ExecutorEngine ExecutorEngine: ShardingJDBC execution engine.
- Collection < PreparedStatementExecutorWrapper > preparedStatemenWrappers a Collection, each Collection is a PreparedStatement wrapper classes, how about this set?
PreparedStatemenWrappers are generated by the routeSQL method.
3) the final call PreparedStatementExecutor method the execute to execute.
Let’s look at the routeSQL and execute methods separately.
4.3 ShardingPreparedStatement# routeSQL
private List<PreparedStatementExecutorWrapper> routeSQL(a) throws SQLException {
List<PreparedStatementExecutorWrapper> result = new ArrayList<>();
SQLRouteResult sqlRouteResult = preparedSQLRouter.route(getParameters()); / / @ 1
MergeContext mergeContext = sqlRouteResult.getMergeContext();
setMergeContext(mergeContext);
setGeneratedKeyContext(sqlRouteResult.getGeneratedKeyContext());
for (SQLExecutionUnit each : sqlRouteResult.getExecutionUnits()) { / / @ 2
PreparedStatement preparedStatement = (PreparedStatement) getStatement(getShardingConnection().getConnection(each.getDataSource(), sqlRouteResult.getSqlStatementType()), each.getSql()); / / @ 3
replayMethodsInvocation(preparedStatement);
getParameters().replayMethodsInvocation(preparedStatement);
result.add(wrap(preparedStatement, each));
}
return result;
}
Copy the code
Code @1: Route calculation according to SQL parameters, this article does not pay attention to the specific implementation details, these will be detailed in the specific analysis of Sharding-JDBC, here is an intuitive look at the results:
Codes @2 and @3: iterate over the results of the sub-database and sub-table, then use the underlying Datasource to create a Connection and create a PreparedStatement object.
This leaves the routeSQL for now, and we know from this that the corresponding Connection and PreparedStatement objects are created based on the routing results using the underlying concrete data source.
4.4 PreparedStatementExecutor# execute
public boolean execute(a) {
Context context = MetricsContext.start("ShardingPreparedStatement-execute");
eventPostman.postExecutionEvents();
final boolean isExceptionThrown = ExecutorExceptionHandler.isExceptionThrown();
final Map<String, Object> dataMap = ExecutorDataMap.getDataMap();
try {
if (1 == preparedStatementExecutorWrappers.size()) { / / @ 1
PreparedStatementExecutorWrapper preparedStatementExecutorWrapper = preparedStatementExecutorWrappers.iterator().next();
return executeInternal(preparedStatementExecutorWrapper, isExceptionThrown, dataMap);
}
List<Boolean> result = executorEngine.execute(preparedStatementExecutorWrappers, new ExecuteUnit<PreparedStatementExecutorWrapper, Boolean>() { / / @ 2
@Override
public Boolean execute(final PreparedStatementExecutorWrapper input) throws Exception {
synchronized (input.getPreparedStatement().getConnection()) {
returnexecuteInternal(input, isExceptionThrown, dataMap); }}});return (null == result || result.isEmpty()) ? false : result.get(0);
} finally{ MetricsContext.stop(context); }}Copy the code
Code @1: If the route information calculated is 1, it is synchronized.
Code @2: If there are more than one calculated route, the thread pool is used to execute asynchronously.
How do you return the result from PreparedStatement#execute? Especially asynchronously.
As mentioned above:
4.4 DefaultResultSetHandler# handleResultSets
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt); / / @ 1
// Omit some of the code, the complete code can be seen in the DefaultResultSetHandler method.
return collapseSingleResultList(multipleResults);
}
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
ResultSet rs = stmt.getResultSet(); / / @ 2
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB 2.1)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else {
if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break; }}}returnrs ! =null ? new ResultSetWrapper(rs, configuration) : null;
}
Copy the code
Let’s look at the key code as follows: The Statement#getResultSet() method is called, and if shardingJdbc is used, ShardingStatement#getResultSet() is called, and the merging of sublibrary and subtable result sets is handled, which I won’t go into detail here, This section has a detailed analysis in shardingjdbc.
Code @2: The common way to get a result set in a JDBC statement is not covered here.
Mybatis ShardingJDBC SQL execution process is introduced here, in order to facilitate the understanding of the above process, finally give the SQL execution flow chart:
The SQL execution process of Mybatis Sharding-Jdbc is introduced here, and the disassembly mechanism of Mybatis can be clearly seen from the figure, which will be introduced in detail in the following.
Welcome to add the author micro signal (DINGwPMZ), add group discussion, the author quality column catalog: 1, source analysis RocketMQ column (40 +) 2, source analysis Sentinel column (12 +) 3, source analysis Dubbo column (28 +) 4, source analysis Mybatis column 5, source analysis Netty column (18 +) 6, source analysis JUC column Source code analysis (MyCat) for Elasticjob