preface

After understanding the initial loading process of MyBatis, we should also investigate how the SQL execution process is executed. In this way, we are familiar with the whole execution process of Mybatis and can quickly locate problems encountered in development.

More importantly, when the interviewer consults the knowledge point of Mybatis in the interview, you can speak out this set of process smoothly, and the interviewer will think that you have mastered the knowledge point of Mybatis, and may not ask. To aim at

This section describes the SQL execution process

After MyBatis initializes the information needed to load the Sql execution process, we can use the SqlSessionFactory object to obtain the SqlSession, and then execute the Sql statement, next look at the specific process of Sql execution, the general execution flow chart is as follows:

Let’s take a look at the specific execution process in each execution link,

SqlSession

SqlSession is MyBatis exposed to external use of the unified interface layer, created by SqlSessionFactory, and it is contains and database dealing with all the operation interface.

The following sequence diagram describes the process of creating a SqlSession object:

Initialize Executor implementation classes based on executorType while generating SqlSession.

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : 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;
  }
Copy the code

The top level SqlSession interface has been generated, so we can look at the SQL execution process what is the next step? How to use the proxy class MapperProxy

MapperProxy

MapperProxy is the key to mapping Mapper interfaces to SQL statements. You can use MapperProxy to bind SQL statements to interfaces. The process is as follows:

  • MapperProxyProxy class generation process
  • MapperProxyThe proxy class performs operations

MapperProxy Proxy class generation process

Where MapperRegistry is a property of Configuration, the Map collection of knownMappers variables of MapperProxyFactory is cached in MapperRegistry when the Configuration is resolved.

The MapperRegistry obtains the cached MapperProxyFactory based on the mapper interface type. The MapperProxyFactory generates the MapperProxy proxy object based on SqlSession.

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: "+ var5, var5); }}}Copy the code

How is MapperProxy implemented when SqlSession interface is called? MyBatis Mapper interface is implemented by dynamic proxy, any method that calls Mapper interface will execute MapperProxy::invoke() method,

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //Object execution
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
			// Interface default method execution
            if (method.isDefault()) {
                if (privateLookupInMethod == null) {
                    return this.invokeDefaultMethodJava8(proxy, method, args);
                }

                return this.invokeDefaultMethodJava9(proxy, method, args); }}catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
Copy the code

However, the mapperMethod::execute() method will be called, mainly to determine whether the operation is an INSERT, UPDATE, DELETE, SELECT statement, and if it is a query, it will also determine the type of return value.

 public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); }}break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            returnresult; }}Copy the code

Through the above analysis, it is concluded that

  • MapperThe actual object of the interface is a proxy object MapperProxy;
  • MapperProxy inheritanceInvocationHandlerTo realizeinvokeMethods;
  • MapperProxyFactory::newInstance()Method based on the JDK dynamic proxy methodMapperProxyProxy class of;
  • Eventually, it will callmapperMethod::execute()Method to complete the operation.
  • And more importantly,MyBatisMyBatis dynamic proxy class diagram structure is shown as follows:

The SqlSession ::selectOne() method is called using SELECT as an example. Further down, the Executor::query() method is executed.

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }
Copy the code

Execute to the Executor class, so what does it have?

Executor

Executor objects are the execution engine of SQL and are responsible for adding, deleting, modifying, and querying data. The top-level interface SqlSession has an Executor object, which can be interpreted as the encapsulated version of JDBC Statement.

Executor is the top-level Executor, which has two implementation classes, BaseExecutor and CachingExecutor

  • BaseExecutor is an abstract class that implements most of the functions defined by the Executor interface, making it easier to implement the interface. BaseExecutor interface adaptation based on the adaptor design pattern has three subclasses, namely SimpleExecutor, ReuseExecutor, and BatchExecutor.

    • SimpleExecutor: the default SimpleExecutor in MyBatis. Every time you perform an update or select, a Statement object is opened and the Statement object is closed immediately

    • ReuseExecutor : Update or select SQL as key to find the Statement object. If the Statement object exists, use it. If the Statement object does not exist, create it. In short, the Statement object is reused

    • BatchExecutor: BatchExecutor used to perform update (there is no select, JDBC batch does not support select to output multiple SQL to the database at once,

  • CachingExecutor: a cache Executor that adds a second level of caching to Executor objects: queries results from the cache and returns the previous results if they exist; If not, delegate to an Executor delegate from the database. The delegate can be any of the above.

In the Mybatis configuration file, you can specify the default ExecutorType ExecutorType, or you can manually pass ExecutorType parameters to the SqlSession creation method of DefaultSqlSessionFactory.

After reading the Exector introduction, continue to trace the execution process link analysis. The JDBC operations in the SqlSession are eventually delegated to the Exector implementation, the Executor:: Query () method. See how Exector is executed.

Each query is executed by the CachingExecutor cache executor, which checks whether the query SQL exists in the level-2 cache. If the query SQL is directly obtained from the level-2 cache, it is executed for the first time. The SQL statement is directly executed and the cache is created. Both are done by the CachingExecutor:: Query () operation.

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.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 level 2 cache corresponding to the query statement
    	Cache cache = ms.getCache();
        // Whether the SQL query exists in level 2 cache
   	    if(cache ! =null) {
            // Check whether the level-2 cache needs to be cleared according to the configuration of 
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                // Query the level 2 cache
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    // The level 2 cache calls the query() method of the encapsulated Executor object without the corresponding result object
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // Save the query results to the level 2 cache
                    this.tcm.putObject(cache, key, list);
                }

                returnlist; }}// The underlying Executor is called directly to perform data database query operations
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
Copy the code

If no value is returned by the CachingExecutor (level 2 cache), BaseExecutor and its implementation class, which defaults to SimpleExecutor, will be executed. SimpleExecutor:: () will eventually be used to query the database.

 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 (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            // Whether to clear the local cache
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                // Get query results from level 1 cache
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                // Get the result
                if(list ! =null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    // If you can't find it, query it from the database
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally{-this.queryStack;
            }

            if (this.queryStack == 0) {
                // Perform lazy loading
                Iterator var8 = this.deferredLoads.iterator();

                while(var8.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache(); }}returnlist; }}Copy the code

How does SimpleExecutor::doQuery() get results from a database query? The execution of myBatis is transferred from Executor to StatementHandler.

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }
Copy the code

Now that our execution link analysis has reached StatementHandler, let’s see how it works

StatementHandler

StatementHandler is responsible for handling the interaction between Mybatis and JDBC Statement, that is, the Statement object interacts with the database. StatementHandler is a top-level interface, with four implementation classes, three of which are the Statement object and database interaction classes. The other one is for routing,

  • RoutingStatementHandler:StatementThe object has no actual operations and is mainly responsible for the other threeStatementHandlerCreate and call, and inMyBatisUsed when executingStatementHandler The interface object is essentiallyRoutingStatementHandlerObject.
  • SimpleStatementHandler: Manages Statement objects, which are used for simple SQL processing.
  • PreparedStatementHandler: Manages Statement objects and preprocesses SQL.
  • CallableStatementHandler: Manages Statement objects, which are used to execute interfaces related to stored procedures.

After going through Executor, Based on the initial load to the StatementType MapperState type through the Configuration. The newStatementHandler RoutingStatementHandler in () method Generate the StatementHandler actual handler class.

 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        switch(ms.getStatementType()) {
        case STATEMENT:
            this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case PREPARED:
            this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        case CALLABLE:
            this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
        default:
            throw new ExecutorException("Unknown statement type: "+ ms.getStatementType()); }}Copy the code

Now PreparedStatementHandler pretreatment, for example, then the Sql execution link to analyze, StatementHandler: : query () to StatementHandler: : execute () really execute Sql query operation.

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.handleResultSets(statement);
  }
Copy the code

But what else can be done before the actual query is executed? ParameterHandler also preprocesses SQL parameters: How does ParameterHandler dynamically map SQL parameters?

ParameterHandler

ParameterHandler a ParameterHandler that sets parameter rules and dynamically assigns values to SQL statements. It has two interfaces

  • GetParameterObject: Reads parameters
  • SetParameters: Used to assign values to parameters in a PreparedStatement

When SimpleExecutor completes PreparedStatementHandler, Invokes the parameterize () method to transfer the PreparedStatement object in SQL ParameterHandler implementation class DefaultParameterHandler: : setParameters () method Placeholder parameter for a PreparedStatement.

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
   	// Parameters are dynamically assigned
    handler.parameterize(stmt);
    return stmt;
  }
Copy the code

DefaultParameterHandler: : setParameters () how to dynamic SQL assignment? The BoundSql object information loaded is used before execution

 public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
     // Gets the wrapper parameterMappings for the list of parameters to be dynamically assigned
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if(parameterMappings ! =null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
          // Whether it is an input parameter
        if(parameterMapping.getMode() ! = ParameterMode.OUT) { Object value;// Get the attribute name of the parameter to be dynamic
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          //// In getting a concrete implementation of parameterMappings using the parse method of SqlSource, we get typeHandler for parameterMappings
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          // Get the JDBC data type
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            //
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
Copy the code

The pretreatment of the execution of the SQL parameter, when StatementHandler: : execute () after the truly perform query operations, have to return the result, need to return the result ResultSetHandler processing, now look at the final result of processing.

ResultSetHandler

ResultSetHandler Result parser, which converts the ResultSet of query results into the corresponding results of mapping (Java DTO, etc.), it has three interfaces

  • handleResultSets(): Processes the result set
  • handleCursorResultSets(): Processes result sets in batches
  • handleOutputParameters(): Processes the result set returned by the stored procedure

Its default implementation is DefaultResultSetHandler and its main functions are:

  • To deal withStatementThe resulting result set after execution produces the relative output,
  • Handles the output parameters of the stored procedure after execution

That see DefaultResultSetHandler: : handleResultSets () how to deal with?

  • When there is more than oneResultSetEach ResultSet corresponds to an Object Object. If stored procedures are not considered, ordinary queries have only one ResultSet
  • ResultSetWrapperEncapsulates theResultSetResult set, whose properties containResultSet ,ResultMapEtc.
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    // When there are multiple resultSets, each ResultSet corresponds to an Object
    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    // Get the first ResultSet object and encapsulate it into a ResultSetWrapper object
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    // Get the ResultMap array
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount); / / < 3.1 > check
    while(rsw ! =null && resultMapCount > resultSetCount) {
        // Get the ResultMap object
        ResultMap resultMap = resultMaps.get(resultSetCount);
        // Process the ResultSet and add the result to the multipleResults
        handleResultSet(rsw, resultMap, multipleResults, null);
        // Get the next ResultSet object and encapsulate it into a ResultSetWrapper object
        rsw = getNextResultSet(stmt);
        / / clean up
        cleanUpAfterHandlingResultSet();
        // resultSetCount ++
        resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if(resultSets ! =null) {
        while(rsw ! =null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if(parentMapping ! =null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; }}// multipleResults are returned with the first element
    return ollapseSingleResultList(multipleResults);
}
Copy the code

In fact, the processing of resultsetthandler result set is quite complicated. Here is just a brief introduction. If you are interested, you can further study it.

After execution, Mybatis SQL execution is basically finished, and the converted result set will be returned to the operator.

conclusion

In the SQL execution process mainly involves the SqlSession, MapperProxy, Executor, StatementHandler, ParameterHandler and ResultSetHandler, including parameter dynamic binding, SQL query execution database data, The results return set mapping, etc., and each link involves a lot of content, each interface can be taken out for separate analysis, then have time to have a detailed look. Next, let’s look at the application of plug-ins.

Is everyone still ok? Like words, move start to show a praise 💗 bai!! Thanks for your support!

Welcome to follow the wechat public number “Ccww Technology Blog”, original technical articles will be launched in the first time