Mybatis interacts with the database in two ways, one is traditional and the other is Mapper agent. Through the analysis of the two ways, we need to master the following contents:

  • The traditional way
  1. MyBatis how to load parsing configuration file?
  2. How does MyBatis parse SQL, set parameters and execute SQL?
  3. How does MyBatis encapsulate the returned result set?
  • Mapper agent mode
  1. How does MyBatis generate proxy objects?
  2. When a proxy object calls a method, how does it execute to the underlying JDBC code?

1 traditional source code analysis

Open source frameworks with high flexibility and extensibility, such as Spring and MyBatis, provide a lot of configuration items, and developers need to provide corresponding configuration information when using to achieve corresponding requirements. There are two configuration files in MyBatis, namely MyBatis -config. XML and mapping configuration file. In addition to XML configuration files, annotations are now the dominant configuration method.

1.1 Initialization Process

The main task of Mybatis initialization is to load and parsemybatis-config.xmlConfiguration file, mapping configuration file, and related annotation information. The following two lines of code can be used to initialize a project that has introduced the MyBatis dependency.

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// This line of code is the beginning of initialization
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
Copy the code

Mybatis initialization entry is the SqlSessionFactoryBuilder is. The build () method. Let’s look at what’s going on in the build() method.

    // 1. The build we originally called
    public SqlSessionFactory build(InputStream inputStream) {
        // Overloaded methods are called
        return build(inputStream, null.null);
    }
    
    // 2. Overloaded methods called
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
            // Create XMLConfigBuilder, XMLConfigBuilder is a class that parses the configuration file of Mybatis
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // Perform XML parsing
            // Create DefaultSqlSessionFactory. The parse() method returns Configuration.
            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

As you can see from the source code above, the build() method is very simple, just call the overloaded build() method, see the comments for details. Next, let’s examine the parse() method of XMLConfigBuilder and the Configuration object. When MyBatis is initialized, all the Configuration information of MyBatis will be loaded into the memory and stored in the Configuration object instance. The Configuration object has almost the same structure as an XML Configuration file, That is, Configuration tags such as Properties, setting, typeAliases and typeHandlers in the XML file have corresponding properties in the Configuration object to encapsulate them. In other words, the essence of initializing Configuration file information is to create a Configuration object and encapsulate parsed XML data into internal Configuration properties.

    /** * 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;
    }

    / * * * * * 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

From the source code, it can be seen that the program is parseConfiguration() to achieve the mybatis-config.xml configuration file of each node to parse, each node of the parse process is packaged into a separate method, clean and concise code.

When the
node is parsed, the XMLMapperBuilder object is created to load the mapping file, and if the mapping configuration file is configured with the corresponding Mapper interface, the corresponding Mapper interface is also loaded, the annotations are parsed and registered with MapperRegistry. When parsing the
tag, the program also completes parsing the SQL, The parsed SQL node information is encapsulated into an MappedStatement object (which describes an SQL statement) and stored in a HashMap attribute mappedStatements in the Configuration object. The map key is ${namespace}.${ID} (fully qualified class name + method name) and value corresponds to the MappedStatement object.

In the analysis of SQL nodes, there are two difficulties, one is the analysis of dynamic SQL, the other is the analysis of

nodes, here only talk about the overall analysis process, these two knowledge points in the form of interview questions separate article analysis.

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

The MappedStatement contains a number of SQL node attributes, including the following:

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

    /** * the id attribute in the node (including the namespace prefix) */
    private String resource;
    
    /** * SqlSource object, corresponding to an SQL statement */
    private SqlSource sqlSource;
    
    SQL statement type (INSERT, UPDATE, DELETE, SELECT, or FLUSH) */
    private SqlCommandType sqlCommandType;

    // Other fields......}Copy the code

Parsing tag details are not introduced here, interested in “MyBatis Technical Insider”. The above is the whole process of loading and parsing the configuration file of MyBatis.

1.2 Executing SQL Processes

After initialization, the program is ready to receive parameters to execute SQL. Before analyzing the SQL process of MyBatis, we need to introduce two interfaces: SqlSession interface and Executor interface.

  1. SqlSession

SqlSession is an interface that has two implementation classes: DefaultSqlSession (default) and SqlSessionManager (deprecated, not described). SqlSession is a top-level class in MyBatis that is used to interact with the database. It is usually bound to ThreadLocal. When controlling transactions, it is necessary to ensure that all database operations in a transaction are performed in a link. And it needs to be closed after use.

public class DefaultSqlSession implements SqlSession {

    private final Configuration configuration;
    private finalExecutor executor; ......}Copy the code

The two most important parameters in SqlSession, Configuration, are the same as at initialization, and Executor is the Executor.

  1. Executor

Executor is also an interface that defines basic methods for database operations. There are three common implementation classes: BatchExecutor (reuses statements and performs batch updates), ReuseExecutor (reuses prepared statements preparedStatement), SimpleExecutor (plain executor, default).

After introducing THE SqlSession and Executor, we will examine the SQL execution process in the traditional way. First, you see the following code, which is the statement that triggers SQL execution.

SqlSession sqlSession = factory.openSession();
List<User> list = sqlSession.selectList("com.silver.mapper.UserMapper.getUserByName");
Copy the code

1.2.1 get SqlSession

OpenSession (); openSession(); openSession();

    // 6. Enter the openSession method
    @Override
    public SqlSession openSession(a) {
        //getDefaultExecutorType() passes SimpleExecutor
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
    }
    
    / / 7. Enter openSessionFromDataSource.
    / / ExecutorType as the Executor of the type, TransactionIsolationLevel for transaction isolation level, whether the autoCommit open transactions
    // Multiple overloaded openSession methods can specify the Executor type of the SeqSession obtained and the handling of the transaction
    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

1.2.2 Executing the SqlSession Interface

After receiving SqlSession, the next step is to execute the SqlSession interface, which is the second line of the traditional code. The parameter is the fully qualified class name + method name. After receiving the parameter, The MappedStatement object is picked up from the Configuration object’s MappedStatement container and then handed over to the Executor to execute. SelectList () ¶

    //8. Enter the selectList method
    @Override
    public <E> List<E> selectList(String statement) {
        return this.selectList(statement, null);
    }

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

    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            // Get the MappedStatement object from the mapped Map based on the fully qualified class name + method name passed in
            MappedStatement ms = configuration.getMappedStatement(statement);
            // Call methods in Executor to process
            // RowBounds is used to handle paging logic
            return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
        } finally{ ErrorContext.instance().reset(); }}Copy the code

1.2.3 Executing the Executor Interface

Continuing with the source code above, enter the executor.query() method.

    // This method is implemented in BaseExecutor, the parent of SimpleExecutor
    @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);
        / / query
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
    
    @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 it is closed, an ExecutorException is thrown
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // Clear the local cache if queryStack is zero and the local cache is required to clear.
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            // queryStack + 1
            queryStack++;
            // Get query results from level 1 cache
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            // If yes, process it
            if(list ! =null) {
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            // If you can't find it, query it from the database
            } else{ list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally {
            // queryStack - 1
            queryStack--;
        }
        if (queryStack == 0) {
            // Perform lazy loading
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            / / empty deferredLoads
            deferredLoads.clear();
            / / if the cache level is LocalCacheScope STATEMENT, to clean up
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482clearLocalCache(); }}return list;
    }
    
    // Read operations from the database
    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
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // Remove the placeholder object from the cache
            localCache.removeObject(key);
        }
        // Add to the cache
        localCache.putObject(key, list);
        // Ignore the stored procedure
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }
    
    // The doQuery() abstract method in SimpleExecutor that implements the superclass
    @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());
            // Run the StatementHandler command to read data
            return handler.query(stmt, resultHandler);
        } finally {
            Close the StatementHandler objectcloseStatement(stmt); }}// Initialize the StatementHandler object
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // Get the Connection object
        Connection connection = getConnection(statementLog);
        // Create a Statement or PrepareStatement object
        stmt = handler.prepare(connection, transaction.getTimeout());
        // Set parameters on the SQL, such as placeholders on the PrepareStatement object
        handler.parameterize(stmt);
        return stmt;
    }
Copy the code

The executor.query () method, after several twists, creates a StatementHandler object, passes the necessary parameters to StatementHandler, uses StatementHandler to complete the database query, and returns the List result set. As you can see from the code above, Executor does:

  1. According to the parameters passed, the DYNAMIC parsing of SQL statements is completed, and BoundSql object is generated for StatementHandler to use.
  2. Create a cache for queries to improve performance;
  3. Create a JDBC Statement connection object and return the List query result to the StatementHandler object at the bottom of the bed.

1. StatementHandler interface

The StatementHandler interface performs two tasks:

  1. For JDBC objects of the type PreparedStatement, where we use SQL statements, how many strings can we create? Placeholder. StatementHandler sets the value of Statement using the parameterize() method.
  2. StatementHandler runs the List query(Statement Statement, ResultHandler ResultHandler) method to execute the Statement and return the Statement object to a resultSet wrapped 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);
    }
    
        @SuppressWarnings("Duplicates")
    @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) {/ / value
                    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 ParameterHandlerጱsetParameters(Statement) method of StatementHandler. ParameterHandlerጱsetParameters(Statement) Specifies the parameters for the Statement object. Placeholder for assignment. StatementHandler List Query (Statement Statement, ResultHandler ResultHandler)

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        // 1. Execute query
        ps.execute();
        // ResultHandler returns the result
        return resultSetHandler.handleResultSets(ps);
    }
Copy the code

1.3 Encapsulate the returned result set

StatementHandler’s List Query (Statement Statement, ResultHandler ResultHandler) The ResultSetHandler handleResultSets(Statement) method is called. The ResultSetHandler’s handleResultSets(Statement) method converts the Statement Statement resultSet into a List resultSet.

    // 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

2 Mapper agent mode

To review the Mapper proxy approach:

    public static void main(String[] args) {
        // The first three steps are identical
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = factory.openSession();

        // Instead of calling the SqlSession API, we get the interface object and call the methods in the interface
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = mapper.getUserByName("tom");
    }
Copy the code

Consider a question: the Mapper interface can be used normally without any implementation classes. Why? The answer is: dynamic proxies.

MapperRegistry is an attribute in the Configuration that maintains a HashMap that holds the factory classes of the Mapper interface, one for each interface. Mappers can configure the package path of the interface, or a specific interface class.

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

When the Mappers tag is parsed, it determines that if it is parsed into a Mapper configuration file, it then wraps the add, delete, change tags in the corresponding configuration file into an MappedStatement object and stores them in mappedStatements. When an interface is parsed, a MapperProxyFactory object corresponding to this interface will be created and stored in HashMap. Key is the bytecode object of the interface, and value is the MapperProxyFactory object corresponding to this interface.

2.1 Mapper Dynamic Proxy

Sqlsession.getmapper (usermapper.class)

    @Override
    public <T> T getMapper(Class<T> type) {
        return configuration.getMapper(type, this);
    }
    
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
    }
    
    @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 dynamic proxy factory.
        try {
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}// 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);
    }
    
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {

        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
Copy the code

2.2 the invoke () method

After the dynamic proxy returns the instance, we can call the method directly from the Mapper class, but the proxy object calls the method, which is actually executed in the Invoke method of the MapperProxy:

    @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 if (isDefaultMethod(method)) {
                returninvokeDefaultMethod(proxy, method, args); }}catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        // Get the MapperMethod object
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // The point is this: MapperMethod finally calls the executed method
        return mapperMethod.execute(sqlSession, args);
    }
    
    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