This paper mainly through the analysis of MyBatis how to execute a SQL statement (process) for the introduction of MyBatis overall framework design.

As the first article of MyBatis series, we must first understand the origin of MyBatis, so before the beginning of the article, we will consider a question: why was MyBatis born?

The birth of 1.

A wheel was born for a reason. JDBC was born to link programs and databases, providing unified access to multiple relational databases. However, the traditional JDBC approach has many disadvantages

Such as:

  • The frequent creation and release of database connections waste system resources, which affects system performance.
  • Sql statements are hardcoded in the code, which makes the code difficult to maintain. Therefore, the Sql may change greatly in actual applications, and the Java code needs to be changed.
  • Hard coding exists in transferring parameters to possession bit symbols in preparedStatement, because the WHERE conditions of SQL statements are not necessarily, which may be more or less. Modifying SQL requires modifying codes, making the system difficult to maintain.
  • Result set parsing is hard coded (query column name), SQL changes lead to parsing code changes, and the system is not easy to maintain.

MyBatis was created to solve the above problems, so it is exactly how to avoid solving the above shortcomings of native JDBC, please read the text to come.


2. Architecture design

There are JDBC issues mentioned above

  1. To solve this problem, we can think of a solution: definitely pooling, using the database connection pool to load and initialize connection resources, pooling management.
  2. The solution to the problem of hard coding SQL statements in the code is to pull the SQL statements out of the XML configuration file without changing the code, and the configuration acts as a bridge between the SQL and the business.
  3. In order to solve this kind of problem, we can think of a solution: using reflection, introspection and other technologies, automatically map entities and tables for attributes and fields.

Of course, I’m not just saying, MyBatis is also doing this, ps :(here we briefly mention, the following article “MBatis easy version” will be introduced in detail)

How does MyBatis architect design

The functional architecture of Mybatis is divided into three layers:

  1. API Interface layer: Interface apis for external use by developers to manipulate the database through these native apis. As soon as the interface layer receives the call request, it calls the data processing layer to complete the specific data processing.

    MyBatis interacts with the database in two ways:

    • Use the traditional MyBatis API

    • Using the Mapper proxy

  2. Data processing layer: responsible for specific SQL lookup, SQL parsing, SQL execution and execution result mapping processing, etc. Its main purpose is to complete a database operation based on the request of the call.

  3. Base support layer: Responsible for the most basic functional support, including connection management, transaction management, configuration loading, and cache handling, all of which are common and extracted as the most basic components. It provides the most basic support for the upper data processing layer.

Major components and their interrelationships

The important modules involved in the above hierarchy are described in detail below:

SqlSession-- "as the main top-level API of MyBatis work, represents the session of interaction with the database, and completes the function of adding, deleting, changing and checking the necessary data library. Executor-- schedules the execution of StatementHandler, ParameterHandler, and ResultHandler SQL statements. StatementHandler-- encapsulates the JDBC Statement operation. Perform operations on JDBC Statements, such as setting parameters and converting the Statement result set to a List. ParameterHandler-- is responsible for converting user-passed parameters into those required for a JDBC Statement. Process SQL parameters. ResultSetHandler-- ResultSet encapsulation processing returns. TypeHandler-- is responsible for mapping and converting between Java and JDBC data types. Article MappedStatement - "MappedStatement maintains a < select | update | delete | insert > node encapsulation. SqlSource-- is responsible for dynamically generating SQL statements based on the parameterObject passed by the user, encapsulating the information into BoundSql objects, and returning them. BoundSql-- indicates the dynamically generated SQL statement and related parameter information.Copy the code

3. Process of executing SQL statements

  1. Load the Configuration file. Using XML as an example, parse the content of the main Configuration file to the Configuration file, load the SQL Configuration information into multiple MapperdStatement objects, and store them in the memory.
  2. The interface layer calls the API provided by MyBatis to pass in the parameter object and pass the request to the lower data processing layer for processing.
  3. In brief, the tasks of the data processing layer are as follows:
    • Parameterhandler searches for the MappedStatement object based on the ID of the SQL statement. SqlSource parses the MappedStatement object. Dynamic SQL generation, obtaining THE SQL statements to be executed and executing the necessary parameters.
    • Executor executes the SQL statement, gets the database connection, executes it to the database based on the resulting final SQL statement and executed parameters, and gets the execution result.
    • According to the result mapping configuration in the MappedStatement object, the ResultSetHandler is transformed and the final result is obtained. Release the connection resource. Finally, the ResultSet ResultSet is returned layer by layer.

A brief description of the general process, so no dynamic text is certainly not suitable for understanding, then is the source code analysis to verify the above views


4. Source code analysis

Taking unit test execution database query as an example, the execution process of initialization to SQL is analyzed.


4.1 the initialization

Start the analysis with standard unit tests as the entry point

     /* Test query */
    @Test
    public void TEST_QUERY_ONE_TO_ONE(a) throws IOException {
        //1. Load the sqlmapconfig. XML configuration file, convert it into an input stream, and store it in the memory
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2. Parse the Configuration file and encapsulate the Configuration object to create the DefaultSqlSessionFactory object
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        // 3. Create DefaultSqlSession instance object with transaction not commit automatically
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //JDK dynamic agents perform JDBC operations
        IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
        List<Order> orderAndUser = mapper.findOrderAndUser();
        for(Order order : orderAndUser) { System.out.println(order); }}Copy the code
//2. Parse the Configuration file and encapsulate the Configuration object to create the DefaultSqlSessionFactory object
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
Copy the code

In terms of code structure, the default SqlSessionFactoryBuilder builds an SqlSessionFactory object. The input parameter is the configuration of SqlMapConfig loaded into the byte stream in the previous step.

   /** * 1. Build entry **@param inputStream
    * @return* /
   public SqlSessionFactory build(InputStream inputStream) {
       // Call overloaded methods
       return build(inputStream, null.null);
   }

   /** * 2. Reload build **@param inputStream
    * @param environment
    * @param properties
    * @return* /
   public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
       try {
           // Create XMLConfigBuilder to parse Mybatis configuration file
           XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
           //parse performs XML parsing
           //build creates the DefaultSqlSessionFactory object
           return build(parser.parse());
       } catch (Exception e) {
           throw ExceptionFactory.wrapException("Error building SqlSession.", e);
       } finally {
           ErrorContext.instance().reset();
           try {
               inputStream.close();
           } catch (IOException e) {
               // Intentionally ignore. Prefer previous error.}}}Copy the code

The key code parser.parse() does its job by parsing SQLmapconfig.xml with XPathParser and loading all the configuration information into memory Using org. Apache. Ibatis. Session. The Configuration instance maintenance (in fact, according to MyBatis Configuration file label positioning Read data encapsulation within the node to the Configuration)


 /** * Parses the XML into a Configuration object. * *@returnThe Configuration object * /
    public Configuration parse(a) {
        // If resolved, throw BuilderException
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // The tag is resolved
        parsed = true;
        // Parser is an XPathParser object that reads data from nodes. < Configuration > is the top-level tag in the MyBatis configuration file
        // Parse the XML Configuration node
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
Copy the code
/ * * * * * what are the specific MyBatis XML parsing XML tags, see the XML mapping profile http://www.mybatis.org/mybatis-3/zh/configuration.html * *@paramRoot Root node */
   private void parseConfiguration(XNode root) {
       try {
           //issue #117 read properties first
           // Parse the 
       tag
           propertiesElement(root.evalNode("properties"));
           // Parse the < Settings /> tag
           Properties settings = settingsAsProperties(root.evalNode("settings"));
           // Load the custom VFS implementation class
           loadCustomVfs(settings);
           // Parse 
       tags
           typeAliasesElement(root.evalNode("typeAliases"));
           // Parse 
       tags
           pluginElement(root.evalNode("plugins"));
           // Parse the 
       tag
           objectFactoryElement(root.evalNode("objectFactory"));
           // Parse 
       tags
           objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
           // Resolve the 
       tag
           reflectorFactoryElement(root.evalNode("reflectorFactory"));
           // Assign < Settings /> to the Configuration property
           settingsElement(settings);
           // read it after objectFactory and objectWrapperFactory issue #631
           // Parse the 
       tag
           environmentsElement(root.evalNode("environments"));
           // Parse the 
       tag
           databaseIdProviderElement(root.evalNode("databaseIdProvider"));
           // Parse the 
       tag
           typeHandlerElement(root.evalNode("typeHandlers"));
           // Parse the 
       tag
           mapperElement(root.evalNode("mappers"));
       } catch (Exception e) {
           throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code

What is Configuration?

The object structure of the MyBatis Configuration core almost corresponds to the XML Configuration file, and the most important one is the MappedStatement

    / * * * * * KEY: MappedStatement mapping ` ${namespace}. ${id} ` * /
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
    
      /** * MapperRegistry object */
    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

Copy the code

MappedStatement and Mapper in the configuration file is a select corresponding/update/insert/delete node. The labels configured in mapper are encapsulated in this object, which is mainly used to describe an SQL statement.


/** * mapping statements, each <select />, < INSERT />, <update />, <delete /> corresponds to an MappedStatement object ** *@author Clinton Begin
 */
public final class MappedStatement {

    /** * The address referenced by the resource */
    private String resource;
    /** * Configuration object */
    private Configuration configuration;
    /** ** id */
    private String id;
    /**
     * 语句类型
     */
    private StatementType statementType;
    /**
     * 结果集类型
     */
    private ResultSetType resultSetType;
      /** * ParameterMap object */
    private ParameterMap parameterMap;
    /** * ResultMap collection */
    private List<ResultMap> resultMaps;
Copy the code

That is, the tags of a custom SQL Mapper configuration file (SELECT, INSERT, Update, DELETE, etc.) are parsed and encapsulated into multiple MappedStatement objects during initialization. MappedStatements is stored in the mappedStatements property of the Configuration object. MappedStatement mapping KEY: ${namespace}.${ID}= class fully qualified name + method name


Following the previous example, after executing the parser.parse() logic in the SqlSessionFactoryBuilder#build method, the DefaultSqlSessionFactory object is created

    /** * Create the DefaultSqlSessionFactory object **@param config
     * @return* /
    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    
Copy the code

Now that the initialization is virtually complete, it’s time to analyze the flow of executing the SQL.


4.2 Executing SQL Processes

The previous step took SqlSessionFactory and used the factory design pattern to create a SqlSession

SqlSession is a top-level class for MyBatis to interact with the database. It is usually bound to ThreadLocal. A session uses one SqlSession and needs to be closed when it is used. DefaultSqlSession (default) and SqlSessionManager (deprecated)

public class DefaultSqlSession implements SqlSession {

    private final Configuration configuration;
    / / actuator
    private final Executor executor;

    private final boolean autoCommit;
    private boolean dirty;
    privateList<Cursor<? >> cursorList;public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
    }
    / / to omit
}
Copy the code

DefaultSqlSession has two important properties: Configuration, which is the MyBatis Configuration core. The other Executor is the Executor, which is actually assigned to perform operations in openSession

Executor is also an interface that has three common implementation classes: BatchExecutor (reuses statements and performs batch updates) ReuseExecutor (reuses prepared statements) SimpleExecutor (common executor, default)Copy the code

     /**
     * openSession
     *
     * @return* /
    @Override
    public SqlSession openSession(a) {
        / / configuration getDefaultExecutorType take SIMPLE SimpleExecutor ()
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
    }

    /** * Multiple overloaded openSession methods can specify the Executor type of SeqSession obtained and the handling of the transaction **@paramExecType is the Executor type *@paramLevel Transaction isolation level *@paramAutoCommit No Start transaction *@return* /
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
            // Get the Environment object
            final Environment environment = configuration.getEnvironment();
            // Create Transaction objects
            final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // Create an Executor object
            final Executor executor = configuration.newExecutor(tx, execType);
            // Create DefaultSqlSession object
            return new DefaultSqlSession(configuration, executor, autoCommit);
        } catch (Exception e) {
            // If an exception occurs, the Transaction object is closed
            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

Since SqlSession is the top-level API interface between MyBatis and the database, the actual execution is left to Executor.

Such as the selectList


 @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }


 private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        try {
            // Fetch the MappedStatement object from the mapped Map based on the fully qualified name + method name passed in
            MappedStatement ms = configuration.getMappedStatement(statement);
            // Calling methods in Executor to handle RowBounds is used for logical paging wrapCollection(parameter) is used to decorate collection or array arguments
            return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
        } finally{ ErrorContext.instance().reset(); }}Copy the code

Before the Executor executes the actual query method, the MappedStatement object is extracted from the Map using the fully qualified name + method name from the Configuration package in the previous step and passed to the Executor

Continue the steps in the source code and enter executor.query()

    /** * The BaseExecutor parent of SimpleExecutor implements **@param ms
     * @param parameter
     * @param rowBounds
     * @param resultHandler
     * @param <E>
     * @return
     * @throws SQLException
     */
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // Dynamically get the SQL statement based on the parameters passed in, and return the BoundSql object
        BoundSql boundSql = ms.getBoundSql(parameter);
        // Create the cached Key for this query
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
Copy the code

The getBoundSql step in the source code is actually the tag processing, the parsing of #{}, the actual parameter replacement value store, complete the placeholder parsing work, parsing out the real SQL statement

This part of cache is skipped for the time being, and the following chapter “MyBatis Level 3 Cache” will be analyzed in detail

Query logic called

    /** * query for overloaded **@param ms
     * @param parameter
     * @param rowBounds
     * @param resultHandler
     * @param key
     * @param boundSql
     * @param <E>
     * @return
     * @throws SQLException
     */
    @SuppressWarnings("unchecked")
    @Override
    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 results from level 1 cache (skip for now)
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            // If it is obtained, it will be processed
            if(list ! =null) {
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // If there is no value in the cache, then query from the database (important)list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally {
            queryStack--;
        }
        if (queryStack == 0) {
            // Perform lazy loading
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            deferredLoads.clear();
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482clearLocalCache(); }}return list;
    }


      /** * query ** from database@param ms
     * @param parameter
     * @param rowBounds
     * @param resultHandler
     * @param key
     * @param boundSql
     * @param <E>
     * @return
     * @throws SQLException
     */
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // In the cache, add placeholder objects. The placeholder here, related to lazy loading, is visible in the 'DeferredLoad#canLoad()' method
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // Perform read operations (key)
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // Remove the placeholder object from the cache
            localCache.removeObject(key);
        }
        // Add to cache (skip for now)
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

Copy the code

DoQuery (MS, parameter, rowBounds, resultHandler, boundSql) is an abstract method.

    /** * The doQuery abstract method for SimpleExecutor that implements the superclass **@param ms
     * @param parameter
     * @param rowBounds
     * @param resultHandler
     * @param boundSql
     * @param <E>
     * @return
     * @throws SQLException
     */
    @Override
    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();
            // Pass the parameter to create the StatementHanlder object to execute the query
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            // Create a JDBC Statement object
            stmt = prepareStatement(handler, ms.getStatementLog());
            // StatementHandler for processing
            return handler.query(stmt, resultHandler);
        } finally {
            / / close StatementHandlercloseStatement(stmt); }}/** * How to create Statement **@param handler
     * @param statementLog
     * @return
     * @throws SQLException
     */
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // The getConnection method in this code will call the openConnection method after many calls to get the connection from the connection pool
        Connection connection = getConnection(statementLog);
        // Create a Statement object
        stmt = handler.prepare(connection, transaction.getTimeout());
        // Set the SQL parameter, PrepareStatement object placeholder
        handler.parameterize(stmt);
        return stmt;
    }

Copy the code

The executor.query () method creates a StatementHandler object, passes the necessary parameters to StatementHandler, and uses StatementHandler to query the database. Finally, the List result set is returned.

To summarize, from the above source code analysis, the message is:

The function of the Executor

  • Based on parameter passing, the SQL statement is parsed and a BoundSql object is dynamically generated for replacement, which is passed to the StatementHandler call
  • Create a JDBCStatement connection object and pass it to the StatementHandler object

So the question is, what is StatementHandler, which was shown in the structure hierarchy diagram that StatementHandler analyzed at the beginning, and what does it do?


4.3 analyze the StatementHandler

The StatementHandler object encapsulates the JDBC Statement operation and performs operations on the JDBC Statement, such as setting parameters and converting the Statement result set to a List. In fact, it does two main things:

  • For JDBC objects of the type PreparedStatement, where we use SQL statements, how many strings can we create? Placeholders, which we will set later. StatementHandler sets the statement using the parameterize(Statement) method.
  • StatementHandler executes the Statement using the List Query (Statement Statement, ResultHandler ResultHandler) method. Encapsulate the resultSet returned by the Statement object as a List.

To implement the parameterize(statement) method of StatementHandler:

    @Override
    public void parameterize(Statement statement) throws SQLException {
        // Use the ParameterHandler object to set the value of Statement
        parameterHandler.setParameters((PreparedStatement) statement);
    }


     @Override
    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        // Iterate over the ParameterMapping array
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if(parameterMappings ! =null) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                // Get the ParameterMapping object
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if(parameterMapping.getMode() ! = ParameterMode.OUT) { Object value; 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);
                    }
                    // Get typeHandler, jdbcType attributes
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    / / set? Placeholder arguments
                    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

Parameterize (Statement) calls the setParameters(Statement) method of ParameterHandler. The ParameterHandler setParameters(Statement) method is responsible for calling the? Statement object based on the parameters we entered. Assignment at the placeholder.

Enter the PreparedStatementHandler#query method

   @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        1. Call preparedStatement. The execute () method,
        // Pass the resultSet to the ResultSetHandler for processing
        PreparedStatement ps = (PreparedStatement) statement;
        // Perform JDBC operations on the database
        ps.execute();
        //2. Use a ResultHandler to process a ResultSet
        return resultSetHandler.handleResultSets(ps);
    }
Copy the code

The StatementHandler query method calls the ResultSetHandler handleResultSets method. The handleResultSets method converts a resultSet generated after the Statement Statement to a List resultSet.

 //
    // HANDLE RESULT SETS
    //
    // process the {@link java.sql.ResultSet} ResultSet

    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

        // A result collection of multiple ResultSets, each corresponding to an Object. In fact, each Object is a List Object.
        When multiple ResultSets are not considered for stored procedures, a common query is actually a single ResultSet, i.e., multipleResults have at most one element.
        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
        // In the case of multiple ResultSets for stored procedures, a normal query actually has only one ResultSet, i.e., a resultMaps element.
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount); / / 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++;
        }

        . / / because ` mappedStatement resultSets ` only used in the process of storage, this series does not consider temporarily, can be ignored
        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 collapseSingleResultList(multipleResults);
    }
Copy the code

You get the ResultSet, you send it back down to the upper level and then you send it to SqlSession, and then an SQL statement is executed.

I do not know if you remember the beginning of the article we led to a figure, after this source analysis, and then aftertaste this figure, think about whether there will be a sense of enlightenment.


4.4 Mapper Agent Mode

Consider a common Mapper interface that we don’t have a way to implement but can use

       //JDK dynamic proxy
        IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
        List<Order> orderAndUser = mapper.findOrderAndUser();
        for (Order order : orderAndUser) {
            System.out.println(order);
        }
Copy the code

Why? The answer is simple: dynamic proxies

As we have just analyzed, when MyBatis is initialized, Configuration internally maintains a HashMap to hold MapperRegistry, the factory class for the Mapper interface. In Mappers, you can configure the package path of the interface, the classpath

<mappers>
 <mapper class="com.mryan.mapper.UserMapper"/>
 <package name="com.mryan.mapper"/>
</mappers>
Copy the code

Mappers tags are wrapped into mappedStatements and stored in mappedStatements for later use. The MapperProxyFactory object corresponding to the interface is then parsed into the Map (key: Class, value: MapperProxyFactory)


public class DefaultSqlSession implements SqlSession {
   / / to omit
 @Override
    public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
    }

    / / to omit
}
Copy the code

The configuration.getMapper(type, this) method is displayed

public class Configuration {
   / / to omit

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }

    / / to omit
}
Copy the code

Continue to go down into the mapperProxyFactory. NewInstance (sqlSession) method


public class MapperRegistry {
   / / to omit

    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // Get the MapperProxyFactory object
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        // If it does not exist, BindingException is thrown
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        // Generate the instance through the JDK dynamic proxy factory.
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}/ / to omit
}
Copy the code
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

    // newInstance in MapperProxyFactory
    public T newInstance(SqlSession sqlSession) {
        MapperProxy creates an implementation class for the JDK dynamic proxy's invocationHandler interface
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        // Overloaded methods are called
        return newInstance(mapperProxy);
    }

Copy the code

MapperProxy is the implementation class created by dynamic proxy, and MapperProxy is the implementation method

public class MapperProxy<T> implements InvocationHandler.Serializable {
Copy the code

JDK dynamic proxy implements the InvocationHandler interface and overrides the Invoke method. Enter the Invoke method and view the source code

  @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // If it is a method defined by Object, call it directly
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else {
                // The point is this: MapperMethod finally calls the executed method
                returncachedInvoker(method).invoke(proxy, method, args, sqlSession); }}catch (Throwable t) {
            throwExceptionUtil.unwrapThrowable(t); }}@Override
        public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
            / / key
            return mapperMethod.execute(sqlSession, args);
        }
Copy the code

Here comes the dawn of victory

SqlSession has insert, UPDATE, SELECT and DELETE. In proxy mode, how does the program know which operation to call which method?

The mapperMethod.execute method is a process of “distribution”. It determines which SqlSession method is called based on the mapper method type

 public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        // Determine the method type in mapper, and ultimately call the method in SqlSession
        switch (command.getType()) {
            case INSERT: {
                // Convert parameters
                Object param = method.convertArgsToSqlCommandParam(args);
                // Perform the INSERT operation
                / / convert the rowCount
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                // Convert parameters
                Object param = method.convertArgsToSqlCommandParam(args);
                / / convert the rowCount
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                // Convert parameters
                Object param = method.convertArgsToSqlCommandParam(args);
                / / convert the rowCount
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
                // If there is no return and a ResultHandler method parameter exists, the query result is submitted to the ResultHandler for processing
                if (method.returnsVoid() && method.hasResultHandler()) {
                    executeWithResultHandler(sqlSession, args);
                    result = null;
                    // Execute the query to return the list
                } else if (method.returnsMany()) {
                    result = executeForMany(sqlSession, args);
                    // Perform the query and return the Map
                } else if (method.returnsMap()) {
                    result = executeForMap(sqlSession, args);
                    // Perform the query, return Cursor
                } else if (method.returnsCursor()) {
                    result = executeForCursor(sqlSession, args);
                    // Perform the query to return a single object
                } else {
                    // Convert parameters
                    Object param = method.convertArgsToSqlCommandParam(args);
                    // Query a single item
                    result = sqlSession.selectOne(command.getName(), param);
                    if (method.returnsOptional() &&
                        (result == null || !method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + command.getName());
        }
        // If the result is null and the return type is primitive, BindingException is thrown
        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 the result
        return result;
    }
Copy the code

So far, the birth of MyBatis, architecture design, initialization source code analysis, the execution of SQL statement source code analysis, MyBatis agent mode, has been analyzed.

Scatter flower 🌹🌹🌹🌹 but not the end, related to MyBatis can not only so, such an excellent framework must have a lot of people learning to find places, the next article, let us into MyBatis three level cache.

trailer

Next article: 🏆 MyBatis level 3 cache

This article is included in the CodeWars series, welcome Star, keep sending out high quality technical articles to me!

For more technical articles, please pay attention to the public number, let’s progress together!