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 整合时事务与代理机制能够正确工作的前提。

MyBatis 核心组件与构建链路 配置来源 mybatis-config.xml Mapper XML / 注解 SqlSessionFactoryBuilder 解析 XML / 注解 build(InputStream) Configuration Environment / DataSource MappedStatement 注册表 SqlSessionFactory 应用级单例 线程安全 SqlSession(非线程安全,一次请求一个) openSession() → 执行 SQL → 提交/回滚 → close() 执行层组件协作 Executor StatementHandler ParameterHandler ResultSetHandler

二、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 本身。

其内部装配流程可以概括为:

  1. 读取 dataSource 属性,构造 Environment,并设置事务工厂为 SpringManagedTransactionFactory,使事务交由 Spring 的 DataSourceTransactionManager 统一管理。
  2. 读取 configLocation(可选),用 XMLConfigBuilder 解析出基础 Configuration。
  3. 读取 mapperLocations,用 XMLMapperBuilder 逐个解析 Mapper XML,把每个 <select>/<insert> 注册为 MappedStatement。
  4. 读取 typeAliasesPackage、plugins、configurationProperties 等,补充类型别名、插件链与全局参数。
  5. 调用 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 是否装配成功,最直接的方式是启动 Spring 容器后观察日志中是否出现 “Parsed mapper file” 与 “Registered MappedStatement” 类信息;若 Mapper XML 路径写错,通常不会立即报错,而是在首次调用 Mapper 方法时抛出 “Invalid bound statement (not found)”。

四、构建结果如何验证

装配完成后,可以通过一个最小测试确认链路是否打通:注入 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 代理、事务传播与一级/二级缓存的基础。