Databases 13 min read

Understanding MyBatis Core Workflow: From Configuration Loading to SQL Execution

This article explains how MyBatis loads its global configuration, parses mapper files, builds SqlSessionFactory and SqlSession, and executes SQL statements through a series of internal components such as Executor, StatementHandler, and caches, illustrated with detailed code snippets.

Top Architect
Top Architect
Top Architect
Understanding MyBatis Core Workflow: From Configuration Loading to SQL Execution

When developing a MyBatis pagination plugin, it is essential to understand the framework's internal workflow, which starts with loading the global XML configuration and ends with executing SQL statements.

Core components involved are:

SqlSession Executor StatementHandler ParameterHandler ResultSetHandler TypeHandler MappedStatement Configuration

The global configuration file typically looks like this:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="sqlMapper/userMapper.xml"/>
    </mappers>
</configuration>

Step 1: Create SqlSessionFactory

The framework parses the XML configuration using XMLConfigBuilder and builds a Configuration object. The key method is:

public Configuration parse() {
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

Mapper files are processed by mapperElement, which can load resources, URLs, or classes:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}

After parsing, the SqlSessionFactory is built:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try { inputStream.close(); } catch (IOException e) { /* ignore */ }
    }
}

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

Step 2: Create SqlSession

Opening a session obtains a JDBC connection and creates an Executor:

public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        Environment environment = configuration.getEnvironment();
        TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

Step 3: Execute SQL

Typical query code: User user = sqlSession.selectOne("test.findUserById", 1); The selectList method retrieves the mapped statement and delegates to the executor:

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

The executor creates a StatementHandler, prepares a Statement, parameterizes it, and finally executes the query:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Connection connection = getConnection(statementLog);
    Statement stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
}

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E>handleResultSets(ps);
}

During execution the framework creates a cache key, checks the local cache, and if missing, reads from the database and stores the result back into the cache. The detailed flow includes methods query, queryFromDatabase, and doQuery in SimpleExecutor.

In summary, the MyBatis execution process consists of:

Generating a BoundSql based on input parameters.

Creating a cache key for the query.

Fetching data from the database when the cache is empty.

Executing the query and caching the result list.

Instantiating a StatementHandler to run the query.

Parameterizing the Statement with actual values.

Calling StatementHandler.query() to obtain the result list.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSQLdatabaseMyBatisORM
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.