MyBatis principle in-depth analysis

The overall architecture

Architecture design

Mybatis can be divided into three layers according to functions:

  • **API Interface layer: ** provides apis for operating databases externally. Mybatis can interact with database in two ways, using sqlSession API, or using Mapper agent;
  • ** Data processing layer: ** is responsible for parameter setting, SQL parsing and execution, and result mapping
  • ** Basic support layer: ** provides basic functional support, including database connection, transaction management, cache configuration, and so on

hierarchy

To complete an SQL operation in Mybatis using sqlSession, go through the following steps.

Access in SqlSession mode

Mybatis provides two ways for us to access the database, one is by calling THE API of sqlSession, the other is by obtaining the proxy object of Mapper interface, directly calling its method. Next, let’s look at how sqlSession interacts with the database.

 public void test1(a) throws IOException {
    // 1. Read the configuration file and convert it to a byte input stream
    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);

    DefaultSqlsession instance object sets the transaction to not commit automatically to complete the creation of the Executor object
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4.(1) According to statementid, the specified MappedStatement object is obtained from the Map set in the Configuration
   // (2) delegate the query task to the Executor
    List<Object> objects = sqlSession.selectList("namespace.id");

    // 5. Release resources
    sqlSession.close();
 }
Copy the code

conclusion

Because the call process is more complex, therefore, the first simple explanation of the overall idea, so not easy to chaos.

SqlSession -> Executor -> StatementHandler -> ParameterHandler -> ResultSetHandler

* * 1. SqlSessionFactory: ** Parses the various tags in the Configuration file with XMLConfigBuilder, saves the Configuration information in the Configuration object, calls the parameter constructor, passes the Configuration object, and creates the DefaultSqlSessionFactory

**2. ** call the SqlSessionFactory#openSession method, create a new SimpleExecutor object, execute the parameter constructor, pass in executor and configuration, and instantiate the DefaultSqlSession object

SqlSession# query: ** Obtain the mappedStatement from the Configuration file according to the Statement ID (namespace+ ID). The executor will perform the query

4. Executor# query:

  • According to the passage, the# {}replace?Placeholders that parse to generate dynamic SQL
  • Setting up the query cache
  • Create the StatementHandler object and delegate the query operation to it

5. StatementHandler# query:

  • Creating a Statement object

  • ParameterHandler takes care of parameter setting. Replace? Placeholders are parameter values

  • Statement invokes the execute method to perform query operations

  • The result set is processed by a ResultSetHandler, or if it is a selectList operation, the List collection is returned

The session factory

Step 1~2 is the process of creating the session factory in Mybatis. Read the Configuration file and save the Configuration information to the Configuration object. The first step is to read the configuration file and convert it to a byte input stream, which we will skip and explore directly how to create a session factory.

SqlSessionFactory

SqlSessionFactoryBuilder#build(java.io.InputStream)

public SqlSessionFactory build(InputStream inputStream) {
    // Overloaded methods are called
    return build(inputStream, null.null);
}
Copy the code
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // Create XMLConfigBuilder(class that parses mybatis configuration files)
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        
        // Perform XML parsing. Parser.parse () returns a Configuration object
        // Call the overload method build
        returnbuild(parser.parse()); }... Omit some code}Copy the code

In the Build method, create an XMLConfigBuilder object that parses the Configuration information converted to a byte stream and saves it in the Configuration object. Next, call the Build overload method again.

Configuration

Configuration is very important, so it is introduced briefly.

The Configuration object is used to store Configuration file information. Its structure is almost identical to that of an XML Configuration file, with each tag of the Configuration file having an internal attribute. XML tags: Settings, typeAliases, objectFactory, mapper, etcCopy the code

Configuration Partial attributes

   /** * variable Properties object. * * see {@linkOrg. Apache. Ibatis. Builder. XML. XMLConfigBuilder# propertiesElement (XNode context)} * /
    protected Properties variables = new Properties();
    /** * ReflectorFactory */
    protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    /** * ObjectFactory */
    protected ObjectFactory objectFactory = new DefaultObjectFactory();

Copy the code

XMLConfigBuilder#parse

XMLConfigBuilder#parse

	/** * Parses the XML into a Configuration object. * *@returnThe Configuration object * /
    public Configuration parse(a) {... Omit some code// Parse the XML Configuration node
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
Copy the code

Call parseConfiguration to parse the mybatis Configuration file and save the Configuration information to the Configuration object

	/ * * * * * 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{... Various tags, omitted// 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
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code

MappedStatement

When parsing Configuration files, tags such as < INSERT > and

are encapsulated into an MappedStatement object and stored in the mappedStatements property of the Configuration object. MappedStatements is a HashMap where the key is the Statement ID (namespace + ID) and the value is the MappedStatement object.

<configuration>
   <! -- Import mapping configuration file -->
    <mappers>
        <mapper resource="UserMapper.xml"></mapper>
    </mappers>
</configuration>


<! -- Namespace: namespace: unique identifier with ID in SQL -->
<mapper namespace="com.lagou.dao.IUserDao">
    <! Add user -->
    <! --parameterType: parameterType -->
    <insert id="saveUser" parameterType="user" >
        insert into user values(#{id},#{username})
    </insert> 
</mapper>
Copy the code
 	/ * * * * * KEY MappedStatement mapping: ` ${namespace}. ${id} ` * /
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
Copy the code

DefaultSqlSessionFactory

At this point, the XML file is parsed and the build method is called to create the default session factory. SqlSessionFactoryBuilder#build

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

conclusion

To create a SqlSessionFactory, read the configuration file and turn it into a byte input stream. The XML tag is then parsed using the XMLConfigBuilder#parseConfiguration method to save the Configuration information to the internal properties of the Configuration object. For SQL in the mapper file, the corresponding MappedStatement is generated. Stored in HashMap, the key is mapper file namespace + method name. Finally, call the parameter constructor, pass in the Configuration object, and create the DefaultSqlSessionFactory

SqlSession

SqlSession is the top-level interface used by Mybatis to interact with the database. It is usually bound to ThreadLocal, and a session uses the same SqlSession, which needs to be closed after use. There are two implementation classes: DefaultSqlSession (default) and SqlSessionManager (deprecated).

DefaultSqlSession has two important parameters: Configuration, which stores parsed configuration information, and Executor, which delegates SQL operations to the executor.

public class DefaultSqlSession implements SqlSession {

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

Create an object

Now that we have analyzed steps 1 and 2, step 3 is to get the sqlSession from openSession() in the session factory.

 public void test1(a) throws IOException {...DefaultSqlsession instance object sets the transaction to not commit automatically to complete the creation of the Executor object
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 4.(1) According to statementid, the specified MappedStatement object is obtained from the Map set in the Configuration
   // (2) delegate the query task to the Executor
    List<Object> objects = sqlSession.selectList("namespace.id");

    // 5. Release resources
    sqlSession.close();
 }
Copy the code

DefaultSqlSessionFactory#openSession()

@Override
public SqlSession openSession(a) {
    //getDefaultExecutorType() passes SimpleExecutor
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
}
Copy the code
/ / ExecutorType as the Executor of the type, TransactionIsolationLevel for transaction isolation level, whether the autoCommit open transactions
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        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 newDefaultSqlSession(configuration, executor, autoCommit); }... Omit some code}Copy the code

Call API

Once we have the session, we can call the relevant API to manipulate the database and continue with step 4

// 4. (1) According to statementid, the specified MappedStatement object is obtained from the Map set in the Configuration
// (2) delegate the query task to the Executor
List<Object> objects = sqlSession.selectList("namespace.id");
Copy the code

Defaultsqlssession #selectList

@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
        MappedStatement ms = configuration.getMappedStatement(statement);
        Query RowBounds -> used for logical paging wrapCollection(parameter) -> used to decorate arrays or collections
        returnexecutor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); }... Omit some code}Copy the code

As you can see, the selectList method retrives the mappedStatements object from the Configuration mappedStatements based on the Statement ID and hands it to the executor, which performs the query.

conclusion

Create an object: The session factory calls the openSession method, which internally executes the parameter constructor of DefaultSqlSession and sets the Configuration and Executor properties.

Calling the API: Get the corresponding mappedStatement from the CongFigUration via Namespace + ID and hand it to the Executor for the database operations

Executor

When we created the SqlSession earlier, the executor we passed was SimpleExecutor, and then we found the corresponding Query method. However, because SimpleExecutor inherits from BaseExecutor and does not rewrite the Query method itself, the BaseExecutor# Query method is executed

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // Dynamically parse the SQL statement based on the parameters passed in, replacing #{} with? Returns BoundSql for use in subsequent prepareStatements
    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
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {... Omit some codetry {
        // Get query results from level 1 cache
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
       
        if(list ! =null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

        } else {
            // If no, read from the databaselist = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}... Omit some codereturn list;
}
Copy the code
// Read operations from the database
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
   
    // Perform read operationslist = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); . Omit some codereturn list;
}
Copy the code

Go ahead and call the SimpleExecutor#doQuery method

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {... Configuration Configuration = Ms. GetConfiguration ();// Create StatementHanlder
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, 
                                                                 rowBounds, resultHandler, boundSql);
    // Create a JDBC Statement object
    stmt = prepareStatement(handler, ms.getStatementLog());
    
    // StatementHandler performs read operations
    return handler.query(stmt, resultHandler);
}
Copy the code

The Executor# Query method eventually creates a StatementHandler object, delegating query operations to the StatementHandler, after several nested method calls.

conclusion

Executor execution process:

1. Complete the dynamic parsing of the SQL statement according to the parameters. Replace #{} with? To generate a BoundSql object for use by the StatementHandler object

2. Create a query cache to improve performance

3. Create a StatementHandler to query the database

StatementHandler

StatementHandler does two things:

  • Parameter setting: SQL parameter setting is accomplished by calling ParameterHandler to replace placeholders
  • Result set encapsulation: Encapsulates the result set returned by the Statement using the ResultSetHandler

Parameter Settings

Continue tracing the code from the doQuery method

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {... Omit some code// Create a JDBC Statement object
    stmt = prepareStatement(handler, ms.getStatementLog());
    // StatementHandler performs read operations
    return handler.query(stmt, resultHandler);
}

// Create a PrepareStatement object and 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;
}


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

Parameterize calls ParameterHandler#setParameters. The parameterize method calls the? Placeholders are replaced with parameter values

Result set encapsulation

Enter the handler#query method to see what happens.

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {... Omit some code// StatementHandler performs read operations
    return handler.query(stmt, resultHandler);
}

Copy the code
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    // Execute the query
    statement.execute(sql);
    // Process the result
    return resultSetHandler.handleResultSets(statement);
}
Copy the code
@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++; }... Omit some code// multipleResults are returned with the first element
    return collapseSingleResultList(multipleResults);
}
Copy the code

The executor calls the Query method, which is essentially the JDBC Statement object that executes the SQL, and then uses the ResultSetHandler to convert the result set into a List.

conclusion

StatementHandler does two things:

ParameterHandler is used to set parameters in SQL. Placeholders are replaced with parameter values

2. Statement executes an SQL statement. The result set is processed by ResultSetHandler

Access in Mapper mode

In addition to calling SqlSession API to operate database directly, Mybatis also provides proxy objects through Mapper interface to complete the interaction with the database.

	/** * Mapper proxy */
    public void test2(a) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = factory.openSession();

        // Use JDK dynamic proxy to generate proxy objects for the mapper interface
        IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);

        // The proxy object invokes any method in the interface, executing the invoke method in the dynamic proxy
        List<Object> allUser = mapper.findAllUser();
    }
Copy the code

MapperRegistry

MapperRegistry is an attribute of Configuration that maintains a HashMap. The key is the class object of the Mapper interface, and the value is the agent factory class corresponding to each interface.

 /**
  * MapperRegistry 对象
  */
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
Copy the code
public class MapperRegistry {
    /** * MyBatis Configuration object */
    private final Configuration config;
    /** * MapperProxyFactory ** KEY: Mapper interface */
    // This class maintains a HashMap containing MapperProxyFactory
    private finalMap<Class<? >, MapperProxyFactory<? >> knownMappers =new HashMap<>();
}
Copy the code

When the Mappers tag is parsed, it determines whether the configuration is an XML file, a Mapper interface, or a package name.

<mappers>
    <mapper resource="UserMapper.xml"></mapper>

    <package name="com.drh.mapper"/>
    <mapper class="com.drh.mapper.UserMapper"></mapper>
</mappers>
Copy the code
  • If it is an XML file, tags such as < INSERT > and

  • If it is the name of the interface or package, a MapperProxyFactory object is created for the Mapper interface and stored in the Map. The key is the class object of the interface and the value is the proxy factory class

    • org.apache.ibatis.binding.MapperRegistry#addMapper

    • public <T> void addMapper(Class<T> type) {
          // To judge, it must be an interface.
          if (type.isInterface()) {
              // If it has been added, BindingException is thrown
              if (hasMapper(type)) {
                  throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
              }
              boolean loadCompleted = false;
              try {
                  // Add to knownMappers
                  knownMappers.put(type, newMapperProxyFactory<>(type)); . Omit some code}}Copy the code

getMapper

To start the analysis, enter the defaultsQlssession #getMapper method

@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}
Copy the code

Configuration#getMapper

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

MapperRegistry#getMapper

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.");
    }
  
    try {
        // Return the proxy object
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}Copy the code

org.apache.ibatis.binding.MapperProxyFactory#newInstance

public T newInstance(SqlSession sqlSession) {
    
    // Create a mapperProxy object that implements the JDK dynamic proxy interface invocationHandler
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    
    // Overloaded methods are called
    return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
    // Call the JDK dynamic proxy to create a Mapper proxy object
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
Copy the code

Call the Mapper method

Now that we know that getMapper returns the proxy object of the Mapper interface, we will intercept the proxy object when we call the method of the Mapper interface. According to the previous analysis, this interception method is the MapPerXyInvoke #invoke method

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {... Omit some code// Get the MapperMethod object
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // The point is this: MapperMethod finally calls the executed method
    return mapperMethod.execute(sqlSession, args);
}
Copy the code

MapperMethod#execute

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()); }... Omit some code// Return the result
    return result;
}
Copy the code

In the mapperXyInvoke #invoke method, create a MapperMethod object and call its execute method. By matching the mapper method type (add/delete/change/query), the final call to sqlSession API to complete the database operation.

conclusion

Mapper accessing a database is divided into two steps: obtaining proxy objects and executing interface methods

  • Get the proxy object:
    • parsingmapperIf an interface or package name is specified, MapperRegistry creates a proxy factory class for each interface and holds the mapping using a HashMap. Key is the class object of the interface
    • Call getMapper, after multi-layer method call (sqlSession -> Configuration -> mapperRegistry), obtain the corresponding Mapper interfaceMapperProxyFactoryThen call the newInstance method. Creates a class that implements the InvocationHandler interfaceMapperProxyObject, finally through JDK dynamic proxy, create Mapper interface proxy object
  • Execute interface methods: When a Mapper method is called, it actually executesMapperProxy#invokeMethods. Create a MapperMethod object, call its execute method, match the mapper method type, and select the API corresponding to SqlSession to complete database operations.