3. MyBatis核心架构与SqlSessionFactory
MyBatis 的运行过程可以理解为“把 XML 或注解中的 SQL 描述,装配成可执行的 Java 对象,再通过会话执行并映射结果”。这条链路的起点不是 SqlSession,而是 Configuration 与 SqlSessionFactory。理解它们,才能理解 Spring 整合 MyBatis 时 SqlSessionFactoryBean 到底做了什么。
一、MyBatis 的核心组件与职责边界
MyBatis 的核心组件大致分为四层:配置层、构建层、会话层、执行层。配置层由 Configuration 统一持有,它相当于 MyBatis 运行期的“全局注册表”,保存了环境信息(数据源、事务工厂)、类型处理器、映射语句、缓存、插件等。构建层由 SqlSessionFactoryBuilder 负责,它读取 XML 配置或 Java 配置,解析后生成一个 Configuration,再据此创建 SqlSessionFactory。会话层由 SqlSessionFactory 负责,它是线程安全的、应用级单例,每次需要操作数据库时通过 openSession() 产生一个 SqlSession。执行层由 Executor、StatementHandler、ParameterHandler、ResultSetHandler 协作完成 SQL 参数绑定、执行与结果映射。
需要特别强调:SqlSessionFactory 一旦构建完成,其内部的 Configuration 原则上不再修改;而 SqlSession 是非线程安全的,必须“一次请求一个会话”,用完关闭。这是后续 Spring 整合时事务与代理机制能够正确工作的前提。
二、Configuration:MyBatis 的全局配置对象
Configuration 是 MyBatis 内部最核心的类,几乎所有可配置项最终都落在它身上。它主要持有以下内容:
- Environment:包含
DataSource与TransactionFactory,决定连接从哪来、事务由谁管理。 - TypeHandlerRegistry:Java 类型与 JDBC 类型之间的转换器注册表,例如
String ↔ VARCHAR、LocalDateTime ↔ TIMESTAMP。 - MapperRegistry:Mapper 接口与 MapperProxyFactory 的映射,使接口方法能被代理调用。
- MappedStatement:每条 SQL 的完整描述,包括 id、SQL 文本、参数映射、结果映射、缓存与超时设置。
- InterceptorChain:插件链,用于分页、SQL 日志、审计等横切逻辑。
在纯 MyBatis 中,我们通常这样构建:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory =
new SqlSessionFactoryBuilder().build(inputStream);
这里的 build() 会解析 XML,把 <environments>、<mappers>、<typeAliases> 等节点翻译成 Configuration 中的字段,最后返回一个 DefaultSqlSessionFactory。注意:SqlSessionFactoryBuilder 是一次性工具,构建完成后即可丢弃,不需要长期持有。
三、Spring 整合:SqlSessionFactoryBean 的装配过程
在 Spring 环境中,我们不再手动调用 SqlSessionFactoryBuilder,而是交给 SqlSessionFactoryBean。它实现了 FactoryBean<SqlSessionFactory>,因此 Spring 容器在调用 getBean("sqlSessionFactory") 时,实际返回的是 getObject() 产出的 SqlSessionFactory,而不是 Bean 本身。
其内部装配流程可以概括为:
- 读取
dataSource属性,构造Environment,并设置事务工厂为SpringManagedTransactionFactory,使事务交由 Spring 的DataSourceTransactionManager统一管理。 - 读取
configLocation(可选),用XMLConfigBuilder解析出基础Configuration。 - 读取
mapperLocations,用XMLMapperBuilder逐个解析 Mapper XML,把每个<select>/<insert>注册为MappedStatement。 - 读取
typeAliasesPackage、plugins、configurationProperties等,补充类型别名、插件链与全局参数。 - 调用
buildSqlSessionFactory(),最终生成DefaultSqlSessionFactory并缓存。
一个典型的 Spring 配置如下(示例参数,非实测结果):
@Configuration
@MapperScan("com.example.order.mapper")
public class MyBatisConfig {
@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost:3306/shop?useSSL=false");
ds.setUsername("app_user");
ds.setPassword("app_pwd");
ds.setMaximumPoolSize(10);
return ds;
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/*.xml"));
bean.setTypeAliasesPackage("com.example.order.domain");
org.apache.ibatis.session.Configuration cfg =
new org.apache.ibatis.session.Configuration();
cfg.setMapUnderscoreToCamelCase(true);
cfg.setCacheEnabled(true);
bean.setConfiguration(cfg);
return bean.getObject();
}
}
这里有两个容易踩坑的点。第一,setConfiguration() 与 setConfigLocation() 不能同时使用,否则会抛出 IllegalStateException,因为二者都试图提供全局配置。第二,mapUnderscoreToCamelCase=true 能让 order_no 自动映射到 orderNo,但前提是结果列名与属性名仅存在下划线差异,否则仍需显式 <resultMap>。
四、构建结果如何验证
装配完成后,可以通过一个最小测试确认链路是否打通:注入 SqlSessionFactory,调用 getConfiguration(),检查 hasStatement("com.example.order.mapper.OrderMapper.selectById") 是否为 true;再通过 openSession() 执行一次查询,观察返回对象字段是否与数据库列正确对应。若 hasStatement 为 false,说明 Mapper XML 未被扫描到或 namespace 与接口全限定名不一致;若查询返回字段为 null,则优先检查 resultMap 或驼峰映射配置。
理解 Configuration → SqlSessionFactoryBuilder → SqlSessionFactory → SqlSession 这条链路,以及 SqlSessionFactoryBean 在 Spring 中如何把数据源、Mapper 位置与全局配置组装成同一个工厂,是后续掌握 Mapper 代理、事务传播与一级/二级缓存的基础。