Deep Dive into MyBatis SQL Execution Process and Custom TypeHandler Implementation
This article explains MyBatis’s internal workflow, covering how mapper interfaces bind to XML files, the step‑by‑step SQL execution pipeline, parameter handling, result set mapping, and how to create custom TypeHandler classes for both input parameters and query results.
Introduction
MyBatis is widely used, but many developers are not familiar with its internal SQL execution flow. This article will help you understand how mapper interfaces are bound to XML files, how SQL statements are processed, and how to create custom TypeHandler implementations.
Overview
Typical programmatic query in MyBatis consists of the following code:
SqlSession session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
List
userList = userMapper.listUserByUserName("孤狼1号");The first line obtains a SqlSession , the second retrieves the UserMapper interface, and the third line executes the whole query with a single method call.
Getting the Mapper Interface (getMapper)
Calling session.getMapper triggers a series of steps:
Configuration retrieves the mapper definition that was loaded during MyBatis startup.
MapperRegistry looks up the mapper class and creates a proxy factory.
The proxy factory generates a JDK dynamic proxy ( MapperProxy ) that implements the mapper interface.
The proxy is stored in knownMappers for later retrieval.
The proxy implements InvocationHandler , which means method calls are delegated to MyBatis’s internal execution logic.
Binding Mapper Interfaces to XML Files
When the mybatis-config.xml is parsed, MyBatis loads all mapper interfaces and their corresponding XML files. The process includes:
Calling SqlSessionFactoryBuilder.build() to create a XMLConfigBuilder .
Parsing the configuration file and locating the <mappers> node.
For each mapper entry, either an XML file or a mapper interface is processed, ultimately linking the interface to its XML statements.
The Configuration.addMapper method stores the mapper in knownMappers .
SQL Execution Process
The execution consists of two major phases: locating the SQL statement and executing it.
Locating SQL
The proxy’s invoke method creates a MapperMethod object that encapsulates method metadata and the associated SQL. It then retrieves the mapped statement from the configuration.
Executing SQL
Depending on the return type, MyBatis calls executeForMany (for collections) or other execution methods. The flow includes:
Parameter conversion via StatementHandler.parameterize .
Creating a PreparedStatement and setting JDBC parameters.
Delegating to PreparedStatementHandler.query , which finally invokes PreparedStatement.execute .
Result handling through ResultSetHandler.handleResultSets , which uses the appropriate TypeHandler to map rows to objects.
Parameter Mapping
MyBatis uses BaseTypeHandler subclasses to set JDBC parameters. For example, StringTypeHandler calls PreparedStatement.setString . Custom TypeHandler s can be defined to control this process.
package com.lonelyWolf.mybatis.typeHandler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.*;
public class MyTypeHandler extends BaseTypeHandler
{
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
System.out.println("Custom TypeHandler applied for setting parameter");
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
System.out.println("Custom TypeHandler applied for retrieving by column name");
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
System.out.println("Custom TypeHandler applied for retrieving by column index");
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
}Using the custom handler in a mapper XML:
<select id="listUserByUserName" parameterType="String" resultMap="MyUserResultMap">
select user_id, user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR,typeHandler=com.lonelyWolf.mybatis.typeHandler.MyTypeHandler}
</select>Result Set Mapping
After the query executes, ResultSetHandler.handleResultSets processes the rows. The handler iterates over the result set, uses the configured TypeHandler to read column values, and populates the result objects.
Workflow Diagram
The diagram below summarizes the main objects involved in MyBatis’s execution flow (SqlSession, Executor, StatementHandler, ParameterHandler, ResultSetHandler).
Conclusion
This article dissected MyBatis’s SQL execution pipeline, demonstrated how mapper interfaces bind to XML files, and showed how to implement custom TypeHandler s for both parameter setting and result mapping. Understanding these mechanisms helps you extend MyBatis when default behavior is insufficient.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.