一、核心设计思想(理解流程的基础)
MyBatis 作为 Java 持久层框架,核心设计围绕两大思想展开:
- 配置驱动:将 SQL 语句、参数映射、结果映射等逻辑从 Java 业务代码中剥离,通过 XML 或注解配置管理,降低业务层与持久层的耦合度,便于 SQL 维护和调整。
- 动态代理:利用 JDK 动态代理为 Mapper 接口生成代理类,拦截接口方法调用,将数据库操作请求转发给 MyBatis 核心执行组件,实现 "接口无实现却能执行 SQL"。
二、核心组件详解(执行流程的核心执行者)
| 组件名 | 核心职责 | 设计细节 |
|---|---|---|
| Configuration | 全局配置中心,存储所有 MyBatis 配置信息 | 单例对象;包含 MappedStatement、数据源、插件、类型处理器等,是 MyBatis 的 "大脑" |
| SqlSessionFactory | 创建 SqlSession 的工厂类 | 基于 Configuration 构建,单例存在,项目启动时初始化一次 |
| SqlSession | 数据库会话,封装执行器与事务管理 | 非线程安全,代表一次数据库连接;业务代码通过它操作数据库 |
| Executor | SQL 执行器,负责 SQL 调度、缓存管理、批处理 | 三种实现:1. SimpleExecutor(默认,简单执行)2. BatchExecutor(批处理)3. CachingExecutor(带二级缓存) |
| MappedStatement | 封装单个 SQL 的所有信息(SQL 语句、参数 / 结果映射、执行方式等) | 对应 Mapper.xml 中的一个 SQL 标签(如 <select>);ID 为 "接口全限定名 + 方法名" |
| ParameterHandler | 解析接口参数,绑定到 SQL 占位符 | 默认实现 DefaultParameterHandler;支持 #{}(预编译)和 ${}(字符串拼接) |
| ResultSetHandler | 将数据库返回的 ResultSet 映射为 Java 对象 | 默认实现 DefaultResultSetHandler;支持简单对象、集合、嵌套结果映射 |
| StatementHandler | 操作 JDBC 的 Statement/PreparedStatement 执行 SQL | MyBatis 与 JDBC 的 "桥梁";负责 SQL 预编译、参数设置、执行触发 |
三、MyBatis 完整执行流程(初始化 + 执行阶段)
以 "根据用户 ID 查询用户信息(userMapper.getById(1L))" 为例,拆解全流程:
(一)初始化阶段:加载配置,构建核心对象
项目启动时完成,为后续执行打下基础。
步骤 1:解析全局配置文件,构建 Configuration
通过 XmlConfigBuilder 解析 mybatis-config.xml,将数据源、事务、映射器等配置存入 Configuration。
// 核心代码
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
XmlConfigBuilder parser = new XmlConfigBuilder(inputStream, null, null);
Configuration configuration = parser.parse(); // 解析配置并生成Configuration
核心逻辑:解析 <environments>(数据源 / 事务)、<mappers>(映射器)等标签,将配置信息统一存入 Configuration 单例。
步骤 2:解析 Mapper.xml,注册 MappedStatement
XmlConfigBuilder 调用 XmlMapperBuilder 解析 Mapper.xml,将每个 SQL 标签封装为 MappedStatement 并注册到 Configuration。实战示例(UserMapper.xml):
xml
<mapper namespace="com.example.mapper.UserMapper">
<select id="getById" parameterType="Long" resultType="com.example.entity.User">
SELECT id, name, age FROM user WHERE id = #{id}
</select>
</mapper>
源码核心逻辑:
// XmlMapperBuilder解析<select>标签
private void processSelectElement(XNode node) {
String id = node.getStringAttribute("id"); // 获取SQL标签id
String parameterType = node.getStringAttribute("parameterType");
String resultType = node.getStringAttribute("resultType");
// 构建MappedStatement
MappedStatement ms = buildMappedStatement(id, parameterType, resultType, node);
// 注册到Configuration(key:com.example.mapper.UserMapper.getById)
configuration.addMappedStatement(ms);
}
步骤 3:构建 SqlSessionFactory
通过 SqlSessionFactoryBuilder 将 Configuration 封装为 SqlSessionFactory(单例),作为 MyBatis 操作数据库的入口。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
(二)执行阶段:从 Mapper 接口调用到结果返回
业务代码调用 Mapper 接口方法时触发,核心是 "动态代理 + 组件协作"。
步骤 1:创建 SqlSession
从 SqlSessionFactory 获取 SqlSession,代表一次数据库连接(非线程安全,建议方法内创建)。
// openSession(true):自动提交事务;默认false(手动提交)
SqlSession sqlSession = sqlSessionFactory.openSession(true);
源码逻辑(DefaultSqlSessionFactory):
@Override
public SqlSession openSession(boolean autoCommit) {
// 获取环境配置(数据源、事务管理器)
Environment environment = configuration.getEnvironment();
// 创建事务
Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), autoCommit);
// 创建Executor(默认SimpleExecutor)
Executor executor = configuration.newExecutor(tx);
// 创建DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
}
步骤 2:生成 Mapper 动态代理对象
通过 SqlSession.getMapper() 生成 Mapper 接口的代理对象,这是 "接口无实现却能执行 SQL" 的核心。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
源码跟踪:
// MapperRegistry.getMapper():获取代理工厂并生成代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
return mapperProxyFactory.newInstance(sqlSession);
}
// MapperProxyFactory.newInstance():生成JDK动态代理
public T newInstance(SqlSession sqlSession) {
// 创建InvocationHandler(代理逻辑实现)
MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 生成代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[]{mapperInterface},
mapperProxy);
}
最终生成的代理对象由 MapperProxy(实现 InvocationHandler)接管,拦截所有接口方法调用。
步骤 3:拦截接口方法调用,转发请求
调用 userMapper.getById(1L) 时,MapperProxy.invoke() 触发,将请求转发给 SqlSession。源码核心:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 过滤Object类方法(如toString())
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 转发请求到SqlSession
return cachedMapperMethod(method).execute(sqlSession, args);
}
// MapperMethod.execute():根据SQL类型调用SqlSession对应方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case SELECT:
result = sqlSession.selectOne(command.getName(), param); // 查询单条
break;
// 省略INSERT/UPDATE/DELETE处理逻辑
}
return result;
}
步骤 4:匹配 MappedStatement,交给 Executor 执行
SqlSession.selectOne() 从 Configuration 中找到对应 MappedStatement,交给 Executor 执行。
// DefaultSqlSession.selectOne()
@Override
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
return list.size() == 1 ? list.get(0) : null;
}
// DefaultSqlSession.selectList()
@Override
public <E> List<E> selectList(String statement, Object parameter) {
// 获取MappedStatement(key:接口全限定名+方法名)
MappedStatement ms = configuration.getMappedStatement(statement);
// 交给Executor执行
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
步骤 5:Executor 调度组件,完成 SQL 执行与结果映射
Executor 协调 ParameterHandler、StatementHandler、ResultSetHandler 完成核心操作:
- 参数绑定 :ParameterHandler 将入参
1L绑定到 SQL 的#{id}占位符,生成 PreparedStatement; - 执行 SQL :StatementHandler 调用
PreparedStatement.execute(),发送 SQL 到数据库; - 结果映射:ResultSetHandler 将数据库返回的 ResultSet 转换为 User 对象。
步骤 6:关闭 SqlSession,释放资源
SqlSession 非线程安全,执行完成后必须关闭,释放数据库连接等资源(建议在 finally 块中执行)。
finally {
if (sqlSession != null) {
sqlSession.close();
}
}
四、设计亮点与性能优化
1. Executor 缓存优化
CachingExecutor 支持二级缓存,缓存查询结果避免重复访问数据库;通过 @CacheNamespace 注解开启 Mapper 级别的二级缓存。
2. 批处理优化(BatchExecutor)
批量插入 / 更新时,使用 ExecutorType.BATCH 创建 SqlSession,减少数据库连接次数,提升性能:
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
3. 参数映射安全优化
#{}:通过 PreparedStatement 预编译处理,避免 SQL 注入,推荐使用;${}:直接字符串拼接,存在 SQL 注入风险,仅适用于固定值(如表名、排序字段)。
五、实战验证:断点调试跟踪流程
通过 IDEA 断点调试,验证执行顺序:
- 在
MapperProxy.invoke()打断点,观察接口方法被拦截; - 在
DefaultSqlSession.selectOne()打断点,观察 MappedStatement 匹配过程; - 在
Executor.query()打断点,观察 SQL 执行与结果映射。
六、完整实战代码示例
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisDemo {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
// 1. 加载配置文件,构建SqlSessionFactory
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. 创建SqlSession
sqlSession = sqlSessionFactory.openSession(true); // 自动提交事务
// 3. 获取Mapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 4. 调用接口方法(触发动态代理与SQL执行)
User user = userMapper.getById(1L);
System.out.println("查询结果:" + user);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 关闭SqlSession
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
// UserMapper接口
public interface UserMapper {
User getById(Long id);
}
// User实体类
class User {
private Long id;
private String name;
private Integer age;
// 省略getter/setter/toString
}
总结
- 核心流程:MyBatis 执行链路为 "配置解析→代理生成→请求转发→组件协作→结果返回",动态代理屏蔽了 Mapper 接口实现细节,配置驱动分离了 SQL 与业务代码;
- 核心组件:Configuration 是配置中心,Executor 是执行调度核心,MappedStatement 封装 SQL 信息,三者是流程的核心支撑;
- 性能优化:二级缓存、批处理 Executor、预编译参数绑定是提升 MyBatis 性能的关键手段。