Article views: 3.2k, likes: 27, favorites: 13. Configuration comes from two sources: configuration files (main config.xml, mapper.xml) and Java code annotations. Main configuration content is parsed and encapsulated into Configuration, and SQL configuration information is loaded as a mappedstatement object and stored in memory.

Architecture Design

MyBatis’s functional architecture is divided into three layers:

  • API Interface Layer: Provides APIs for external use. Developers operate databases through these local APIs. When the interface layer receives a call request, it calls the data processing layer to complete specific data processing. (MyBatis interacts with the database in two ways: through MyBatis API and through Mapper proxy)
  • Data Processing Layer: Responsible for specific SQL lookup, SQL parsing, SQL execution, and execution result mapping processing. Its main purpose is to complete one database operation according to the call request.
  • Foundation Support Layer: Responsible for the most basic functional support, including connection management, transaction management, configuration loading, and cache processing. These are common things that have been extracted as the most basic components to provide the most fundamental support for the upper data processing layer.

Main Components

Overall Process

Load Configuration Initialization

Trigger condition: Load configuration file Configuration comes from two sources: configuration files (main config.xml, mapper.xml) and Java code annotations. Main configuration content is parsed and encapsulated into Configuration, and SQL configuration information is loaded as a mappedstatement object and stored in memory.

Receive Call Request

  • Trigger condition: Call MyBatis API
  • Input parameters: SQL ID and input parameter object
  • Processing: Pass the request to the lower-level request processing layer for processing

Process Operation Request

Trigger condition: API interface layer passes request Input parameters: SQL ID and input parameter object Processing:

  • Find corresponding MappedStatement object based on SQL ID
  • Parse MappedStatement object based on input parameter object to get final SQL to execute and object to execute
  • Get database connection, execute final SQL statement and input parameters to database based on obtained information, and get results
  • Based on result mapping configuration in MappedStatement object, convert obtained execution results and get final processing results
  • Release connection resources
  • Return processing results

Source Code Analysis

Initialization

Inputstream inputstream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

In the initialization build method:

// 1. The initially called build method
public SqlSessionFactory build(InputStream inputStream) {
    // Call overloaded method, passing default environment and properties
    return build(inputStream, null, null);
}

// 2. Overloaded build method
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // XMLConfigBuilder is responsible for parsing MyBatis configuration files
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

        // Parse configuration file and return Configuration object, then call overloaded build method
        return build(parser.parse());
    } catch (Exception e) {
        // Exception handling: wrap exception as runtime exception and throw
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    }
}

During MyBatis initialization, all MyBatis configuration information is loaded into memory, maintained using org.apache.ibatis.session.Configuration instance.

/**
 * Parse XML configuration and convert to Configuration object.
 */
public Configuration parse() {
    // If already parsed, throw BuilderException
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }

    // Mark as parsed
    parsed = true;

    // Parse XML configuration root node
    parseConfiguration(parser.evalNode("/configuration"));

    return configuration;
}

/**
 * Parse XML configuration nodes and handle each tag.
 */
private void parseConfiguration(XNode root) {
    try {
        // Parse each XML configuration part in order

        // 1. Parse <properties /> tag
        propertiesElement(root.evalNode("properties"));

        // 2. Parse <settings /> tag and convert to Properties object
        Properties settings = settingsAsProperties(root.evalNode("settings"));

        // 3. Load custom VFS implementation class
        loadCustomVfs(settings);

        // 4. Parse <typeAliases /> tag
        typeAliasesElement(root.evalNode("typeAliases"));

        // 5. Parse <plugins /> tag
        pluginElement(root.evalNode("plugins"));

        // 6. Parse <objectFactory /> tag
        objectFactoryElement(root.evalNode("objectFactory"));

        // 7. Parse <objectWrapperFactory /> tag
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

        // 8. Parse <reflectorFactory /> tag
        reflectorFactoryElement(root.evalNode("reflectorFactory"));

        // 9. Assign parsed settings configuration to Configuration object
        settingsElement(settings);

        // 10. Parse <environments /> tag
        environmentsElement(root.evalNode("environments"));

        // 11. Parse <databaseIdProvider /> tag
        databaseldProviderElement(root.evalNode("databaseldProvider"));

        // 12. Parse <typeHandlers /> tag
        typeHandlerElement(root.evalNode("typeHandlers"));

        // 13. Parse <mappers /> tag
        mapperElement(root.evalNode("mappers"));

    } catch (Exception e) {
        // Catch and throw custom exception during parsing
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

Introducing MappedStatement: MappedStatement corresponds to a select/update/insert/delete node in a Mapper configuration file. All tags configured in mapper are encapsulated into this object, whose main purpose is to describe an SQL statement. During the introduction of configuration files, all tags in mybatis-config.xml are parsed, including the mappers tag used to import mapper.xml files or configure mapper interface directories.

<select id="getUser" resultType="user" >
  select * from user where id=#{id}
</select>

A select tag is parsed and encapsulated into a MappedStatement object during initialization, then stored in Configuration object’s MappedStatements attribute, which is a HashMap with key=fully qualified class name + method name, value=MappedStatement object.

In XMLConfigBuilder processing:

private void parseConfiguration(XNode root) {
    try {
        // Omit handling of other tags
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

At this point, XML configuration file parsing ends, returning to step 2 to call overloaded build method.

public SqlSessionFactory build(Configuration config) {
    // Created DefaultSqlSessionFactory object
    return new DefaultSqlSessionFactory(config);
}

SqlSession

First, briefly introduce SqlSession. SqlSession is an interface with two implementation classes:

  • DefaultSqlSession
  • SqlSessionManager

SqlSession is a top-level class in MyBatis for database interaction. It is usually bound with ThreadLocal. One session uses one SqlSession, and close() needs to be called after finishing.

public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private final Executor executor;
    // Omit
}

Most important parameters in SqlSession:

  • Configuration same as during initialization
  • Executor is the executor

Executor

Executor is also an interface with three commonly used implementation classes:

  • BatchExecutor: Reuses statements and executes batch updates
  • ReuseExecutor: Reuses prepared statements
  • SimpleExecutor: Ordinary executor (default)

When we obtain SqlSession:

// 6. Enter openSession method.
public SqlSession openSession() {
    // getDefaultExecutorType() passes SimpleExecutor
    // Create SqlSession through openSessionFromDataSource method
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

// 7. Enter openSessionFromDataSource method.
// ExecutorType is the type of Executor, TransactionIsolationLevel is transaction isolation level, autoCommit is whether to enable transaction
// Multiple overloaded methods of openSession can specify the Executor type of obtained SqlSession and transaction handling
private SqlSession openSessionFromDataSource(ExecutorType execType,
                                            TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // Get current environment configuration
        final Environment environment = configuration.getEnvironment();

        // Get transaction factory
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

        // Create transaction object, passing data source, transaction isolation level and whether to auto-commit
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

        // Create Executor based on specified Executor type and transaction
        final Executor executor = configuration.newExecutor(tx, execType);

        // Return DefaultSqlSession object, passing configuration, executor and autoCommit status
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        // Catch exception, close transaction, may have obtained database connection, so need to close
        closeTransaction(tx);
        // Re-throw exception or handle exception logic
        throw new RuntimeException("Error occurred while opening session", e);
    }
}

Subsequently, you can follow the query method. Finally, a StatementHandler object is created, and necessary parameters are passed to StatementHandler. StatementHandler is used to complete database queries and return List result set.

From the above code, we can see the functions and roles of Executor:

  • Complete dynamic SQL parsing based on passed parameters, generate Bound SQL object for StatementHandler use
  • Create cache for queries to improve performance
  • Create JDBC Statement connection object, pass to StatementHandler object, return List query object.

StatementHandler

StatementHandler object mainly completes two tasks:

  • During the creation of JDBC PreparedStatement type objects, the SQL statement string used contains several placeholders. StatementHandler sets values for Statement through the parameter(statement) method
  • StatementHandler completes executing Statement through List query method, encapsulating the resultSet returned by Statement object into List

Enter StatementHandler’s parameterize method implementation:

public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
}