乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

SqlSessionFactoryBuilder与openSession重载

导学

通过本节学习,你将能够:

  • 掌握 SqlSessionFactoryBuilder 全部 5 个 build() 方法的重载差异与适用场景
  • 理解 SqlSessionFactory 的 8 个 openSession() 重载,灵活控制自动提交、隔离级别、执行器类型
  • 熟练使用 TransactionIsolationLevel 枚举设置事务隔离级别
  • 根据业务特点(事务要求、批量操作、外部连接复用)选择正确的 openSession() 重载
  • 写出支持多环境切换、属性覆盖的工厂构建代码

定义

SqlSessionFactoryBuilder 是什么

SqlSessionFactoryBuilder 是 MyBatis 的工厂构建器,负责读取配置源(XML 输入流或编程式 Configuration 对象),解析并创建全局唯一的 SqlSessionFactory。它的设计哲学是**"用过即丢"**——一旦 Factory 创建完成,Builder 即可被垃圾回收。

SqlSessionFactory 的 openSession() 是什么

openSession() 是 SqlSessionFactory 的工厂方法,用于创建与数据库交互的 SqlSession 实例。通过不同的重载,开发者可以精确控制新 Session 的自动提交行为、事务隔离级别、执行器类型以及外部连接复用。

解决了什么痛点

在 JDBC 原始写法中,开发者面对以下困境:

  1. 环境切换困难:开发库、测试库、生产库的连接信息散落在代码各处,切换环境需要改多处代码
  2. 事务控制僵化:Connection 的自动提交和隔离级别一旦设置,整个应用难以按需调整
  3. 批量操作低效:大量插入时,每条 SQL 单独发送网络请求,性能极差
  4. 外部连接复用:与 Spring 等框架集成时,需要让 MyBatis 使用框架管理的数据库连接

MyBatis 通过 build() 的多重载支持配置源多样化和属性动态覆盖,通过 openSession() 的多重载支持会话行为精细化控制。


核心原理

build() 与 openSession() 完整流程图

流程图解读:

  1. 构建阶段:build() 方法首先解析 XML 或接收现成的 Configuration,生成全局配置对象,再封装为 SqlSessionFactory。外部传入的 Properties 会覆盖 XML 中同名的占位符变量(如 ${jdbc.url})。
  2. 环境隔离:XML 中可配置多个 <environment>,build(is, env) 通过 id 切换不同数据库连接,实现开发/测试/生产环境一键切换。
  3. 会话阶段:openSession() 根据参数组合创建不同特性的 Executor,最终包装为 SqlSession。ExecutorType 决定了 SQL 语句的预编译和复用策略。
  4. 外部连接:openSession(Connection) 系列重载让 MyBatis 放弃从连接池获取连接,转而使用调用方传入的 Connection,这是与 Spring 事务管理器集成的关键入口。

完整方法速查表

SqlSessionFactoryBuilder 的 5 个 build() 方法

方法签名参数说明适用场景
build(InputStream inputStream)从 XML 输入流构建,使用 XML 中 default 指定的环境单环境项目,最常用
build(InputStream inputStream, String environment)指定环境 ID,覆盖 XML 中的 default多环境项目(dev/test/prod)动态切换
build(InputStream inputStream, Properties properties)额外传入属性,覆盖 XML 中的占位符敏感信息(密码)不从 XML 读取,外部注入
build(InputStream inputStream, String environment, Properties properties)同时指定环境和外部属性多环境 + 外部属性注入的复合场景
build(Configuration config)直接传入编程式创建的 Configuration 对象纯代码配置(无 XML),或高级定制后传入

SqlSessionFactory 的 8 个 openSession() 重载

方法签名参数说明适用场景
openSession()无参数,默认配置常规 CRUD,手动控制事务(推荐)
openSession(boolean autoCommit)true 自动提交,false 手动提交简单查询场景可开自动提交;DML 需事务时关自动提交
openSession(Connection connection)使用外部提供的 JDBC 连接与 Spring 集成,复用框架管理的事务连接
openSession(TransactionIsolationLevel level)指定事务隔离级别需要显式控制脏读、不可重复读、幻读的业务
openSession(ExecutorType execType)指定执行器类型批量操作选 BATCH,重复语句选 REUSE
openSession(ExecutorType execType, boolean autoCommit)执行器类型 + 自动提交批量插入且需要手动控制提交边界
openSession(ExecutorType execType, TransactionIsolationLevel level)执行器类型 + 隔离级别批量操作且对事务隔离有要求
openSession(ExecutorType execType, Connection connection)执行器类型 + 外部连接Spring 集成下使用 BATCH 执行器进行批量操作

TransactionIsolationLevel 枚举

枚举值隔离级别脏读不可重复读幻读适用场景
NONE无隔离✓ 允许✓ 允许✓ 允许仅查询、无事务要求
READ_UNCOMMITTED读未提交✓ 允许✓ 允许✓ 允许极少使用,性能优先且容忍脏读
READ_COMMITTED读已提交✗ 禁止✓ 允许✓ 允许Oracle/SQL Server 默认,大多数业务首选
REPEATABLE_READ可重复读✗ 禁止✗ 禁止✓ 允许MySQL 默认,需要防止不可重复读
SERIALIZABLE串行化✗ 禁止✗ 禁止✗ 禁止强一致性要求,性能代价最大

ExecutorType 枚举

枚举值说明适用场景
SIMPLE为每条语句创建新的 PreparedStatement,执行后关闭默认类型,通用场景
REUSE复用 PreparedStatement,缓存于 Map<String, Statement> 中同一会话内重复执行相同结构的 SQL,减少编译开销
BATCH将多条修改语句(insert/update/delete)批量发送,减少网络往返大批量插入/更新,但 select 可能返回不正确结果

完整示例

操作前的数据库表结构及初始数据

CREATE TABLE student (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20),
    age INT,
    major VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);

场景一:使用不同 build() 方法构建 SqlSessionFactory(展示环境切换和属性覆盖)

需求:同一个 mybatis-config.xml 中配置开发环境和生产环境,演示通过不同 build() 重载切换环境,以及通过外部 Properties 覆盖数据库密码。

mybatis-config.xml(多环境配置):

<?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>
    <properties resource="db.properties"/>
    <typeAliases>
        <package name="com.fly.entity"/>
    </typeAliases>
    <!-- 配置两个环境:development 和 production -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.dev.url}"/>
                <property name="username" value="${jdbc.dev.username}"/>
                <property name="password" value="${jdbc.dev.password}"/>
            </dataSource>
        </environment>
        <environment id="production">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.prod.url}"/>
                <property name="username" value="${jdbc.prod.username}"/>
                <property name="password" value="${jdbc.prod.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/StudentMapper.xml"/>
    </mappers>
</configuration>

db.properties(基础属性文件):

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.dev.url=jdbc:mysql://localhost:3306/school_dev?useSSL=false&serverTimezone=Asia/Shanghai
jdbc.dev.username=dev_user
jdbc.dev.password=dev_pass
jdbc.prod.url=jdbc:mysql://192.168.1.100:3306/school_prod?useSSL=false&serverTimezone=Asia/Shanghai
jdbc.prod.username=prod_user
jdbc.prod.password=prod_pass

测试代码:

package com.fly.test;

import com.fly.entity.Student;
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;
import java.util.List;
import java.util.Properties;

public class BuildMethodDemo {

    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";

        // ========== 方式 1:build(InputStream) —— 使用默认环境 ==========
        System.out.println("========== build 方式 1:使用默认环境(development)==========");
        try (InputStream is = Resources.getResourceAsStream(resource)) {
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
            try (SqlSession session = factory.openSession()) {
                List<Student> list = session.selectList("com.fly.mapper.StudentMapper.findAll");
                System.out.println("默认环境查询到 " + list.size() + " 条记录");
            }
        }

        // ========== 方式 2:build(InputStream, environment) —— 切换到生产环境 ==========
        System.out.println("\n========== build 方式 2:显式指定 production 环境 ==========");
        try (InputStream is = Resources.getResourceAsStream(resource)) {
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is, "production");
            System.out.println("【环境切换】已切换到 production 环境,连接至 192.168.1.100");
            // 实际执行查询...
        }

        // ========== 方式 3:build(InputStream, Properties) —— 外部属性覆盖密码 ==========
        System.out.println("\n========== build 方式 3:外部 Properties 覆盖数据库密码 ==========");
        try (InputStream is = Resources.getResourceAsStream(resource)) {
            // 从环境变量或配置中心读取真实密码,覆盖 XML 中的占位符
            Properties externalProps = new Properties();
            externalProps.setProperty("jdbc.dev.password", System.getenv("DB_DEV_PASSWORD"));
            // 若环境变量未设置,使用默认值演示
            if (externalProps.getProperty("jdbc.dev.password") == null) {
                externalProps.setProperty("jdbc.dev.password", "override_dev_123");
            }

            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is, externalProps);
            System.out.println("【属性覆盖】外部 Properties 已覆盖 jdbc.dev.password");
            System.out.println("【安全提示】生产环境密码不应硬编码在 XML 或 properties 文件中");
        }

        // ========== 方式 4:build(InputStream, environment, Properties) —— 复合场景 ==========
        System.out.println("\n========== build 方式 4:指定环境 + 外部属性覆盖 ==========");
        try (InputStream is = Resources.getResourceAsStream(resource)) {
            Properties externalProps = new Properties();
            externalProps.setProperty("jdbc.prod.password", "override_prod_456");

            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is, "production", externalProps);
            System.out.println("【复合配置】环境=production,且密码被外部属性覆盖");
        }

        // ========== 方式 5:build(Configuration) —— 纯代码配置 ==========
        System.out.println("\n========== build 方式 5:纯代码构建 Configuration ==========");
        org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
        config.setEnvironment(new org.apache.ibatis.mapping.Environment(
            "code_env",
            new org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory(),
            new org.apache.ibatis.datasource.pooled.PooledDataSource(
                "com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://localhost:3306/school_dev?useSSL=false&serverTimezone=Asia/Shanghai",
                "root",
                "123456"
            )
        ));
        config.addMapper(com.fly.mapper.StudentMapper.class);

        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);
        try (SqlSession session = factory.openSession()) {
            List<Student> list = session.selectList("com.fly.mapper.StudentMapper.findAll");
            System.out.println("纯代码配置查询到 " + list.size() + " 条记录");
        }
    }
}

实际执行结果:

========== build 方式 1:使用默认环境(development)==========
默认环境查询到 5 条记录

========== build 方式 2:显式指定 production 环境 ==========
【环境切换】已切换到 production 环境,连接至 192.168.1.100

========== build 方式 3:外部 Properties 覆盖数据库密码 ==========
【属性覆盖】外部 Properties 已覆盖 jdbc.dev.password
【安全提示】生产环境密码不应硬编码在 XML 或 properties 文件中

========== build 方式 4:指定环境 + 外部属性覆盖 ==========
【复合配置】环境=production,且密码被外部属性覆盖

========== build 方式 5:纯代码构建 Configuration ==========
纯代码配置查询到 5 条记录

结果分析:

build 重载核心能力典型用途
build(is)使用 XML 默认环境单环境快速启动
build(is, env)环境切换开发/测试/生产多环境部署
build(is, props)属性覆盖密码从配置中心或环境变量注入
build(is, env, props)环境 + 属性生产环境切换 + 密码外部化
build(config)纯代码配置无 XML 的轻量级项目,或高级定制

场景二:使用不同 openSession() 重载(展示 autoCommit、隔离级别、执行器类型的选择)

需求:针对乐途公司学生管理系统的不同业务场景,选择最合适的 openSession() 重载。

StudentMapper.xml 中的批量插入语句:

<!-- 批量插入需要的环境 -->
<insert id="insertStudent" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO student (name, age, major, score)
    VALUES (#{name}, #{age}, #{major}, #{score})
</insert>

测试代码:

package com.fly.test;

import com.fly.entity.Student;
import com.fly.util.MyBatisUtil;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;

import java.math.BigDecimal;
import java.sql.Connection;
import java.util.List;

public class OpenSessionDemo {

    public static void main(String[] args) {
        SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory();

        // ========== 方式 1:openSession() —— 默认手动提交,SIMPLE 执行器 ==========
        System.out.println("========== openSession 方式 1:默认配置(推荐)==========");
        try (SqlSession session = factory.openSession()) {
            System.out.println("【配置】autoCommit=" + session.getConnection().getAutoCommit()
                + ", 执行器=SIMPLE");
            Student student = session.selectOne("com.fly.mapper.StudentMapper.findById", 1);
            System.out.println("查询结果: " + student.getName());
            // DML 需要手动 commit
        } catch (Exception e) {
            e.printStackTrace();
        }

        // ========== 方式 2:openSession(true) —— 自动提交 ==========
        System.out.println("\n========== openSession 方式 2:自动提交模式 ==========");
        try (SqlSession session = factory.openSession(true)) {
            System.out.println("【配置】autoCommit=true,每条 DML 自动提交");
            Student newbie = new Student("自动提交学生", 20, "测试专业", new BigDecimal("80.00"));
            int rows = session.insert("com.fly.mapper.StudentMapper.insertStudent", newbie);
            System.out.println("【插入】影响行数: " + rows + "(已自动提交,无需手动 commit)");
            // 注意:此模式下无法 rollback
        } catch (Exception e) {
            e.printStackTrace();
        }

        // ========== 方式 3:openSession(TransactionIsolationLevel) —— 显式隔离级别 ==========
        System.out.println("\n========== openSession 方式 3:指定 READ_COMMITTED ==========");
        try (SqlSession session = factory.openSession(TransactionIsolationLevel.READ_COMMITTED)) {
            Connection conn = session.getConnection();
            System.out.println("【配置】隔离级别=" + conn.getTransactionIsolation()
                + "(Connection.TRANSACTION_READ_COMMITTED=" + Connection.TRANSACTION_READ_COMMITTED + ")");
            List<Student> list = session.selectList("com.fly.mapper.StudentMapper.findAll");
            System.out.println("查询到 " + list.size() + " 条记录");
        } catch (Exception e) {
            e.printStackTrace();
        }

        // ========== 方式 4:openSession(ExecutorType.BATCH) —— 批量执行器 ==========
        System.out.println("\n========== openSession 方式 4:BATCH 执行器(批量插入)==========");
        try (SqlSession session = factory.openSession(ExecutorType.BATCH)) {
            System.out.println("【配置】ExecutorType=BATCH,多条 INSERT 合并发送");
            String statement = "com.fly.mapper.StudentMapper.insertStudent";

            // 模拟批量插入 3 条记录
            session.insert(statement, new Student("批量学生A", 20, "批量专业", new BigDecimal("70.00")));
            session.insert(statement, new Student("批量学生B", 21, "批量专业", new BigDecimal("75.00")));
            session.insert(statement, new Student("批量学生C", 22, "批量专业", new BigDecimal("80.00")));

            // BATCH 模式下,insert 返回的是批量队列中的序号,而非真实影响行数
            // 必须 flushStatements 后才能获取真实结果
            List<org.apache.ibatis.executor.BatchResult> batchResults = session.flushStatements();
            int totalRows = batchResults.stream().mapToInt(r -> r.getUpdateCounts().length).sum();
            System.out.println("【批量执行】待提交语句数: " + batchResults.size() + ", 影响记录数: " + totalRows);

            session.commit();
            System.out.println("【提交】批量插入已提交");
        } catch (Exception e) {
            e.printStackTrace();
        }

        // ========== 方式 5:openSession(ExecutorType, boolean) —— 执行器 + 自动提交 ==========
        System.out.println("\n========== openSession 方式 5:REUSE + 手动提交 ==========");
        try (SqlSession session = factory.openSession(ExecutorType.REUSE, false)) {
            System.out.println("【配置】ExecutorType=REUSE,复用 PreparedStatement");
            String statement = "com.fly.mapper.StudentMapper.findById";
            // 同一会话内多次执行相同 SQL,PreparedStatement 会被复用
            Student s1 = session.selectOne(statement, 1);
            Student s2 = session.selectOne(statement, 2);
            Student s3 = session.selectOne(statement, 3);
            System.out.println("复用查询结果: " + s1.getName() + ", " + s2.getName() + ", " + s3.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }

        // ========== 最终验证数据状态 ==========
        System.out.println("\n========== 最终数据状态 ==========");
        try (SqlSession session = factory.openSession()) {
            List<Student> list = session.selectList("com.fly.mapper.StudentMapper.findAll");
            System.out.println("当前共 " + list.size() + " 条记录:");
            list.forEach(s -> System.out.println("  " + s));
        }
    }
}

实际执行结果:

========== openSession 方式 1:默认配置(推荐)==========
【配置】autoCommit=false, 执行器=SIMPLE
查询结果: 大翔

========== openSession 方式 2:自动提交模式 ==========
【配置】autoCommit=true,每条 DML 自动提交
==>  Preparing: INSERT INTO student (name, age, major, score) VALUES (?, ?, ?, ?)
==> Parameters: 自动提交学生(String), 20(Integer), 测试专业(String), 80.00(BigDecimal)
<==    Updates: 1
【插入】影响行数: 1(已自动提交,无需手动 commit)

========== openSession 方式 3:指定 READ_COMMITTED ==========
【配置】隔离级别=2(Connection.TRANSACTION_READ_COMMITTED=2)
==>  Preparing: SELECT id, name, age, major, score FROM student
==> Parameters: 
<==      Total: 7
查询到 7 条记录

========== openSession 方式 4:BATCH 执行器(批量插入)==========
【配置】ExecutorType=BATCH,多条 INSERT 合并发送
==>  Preparing: INSERT INTO student (name, age, major, score) VALUES (?, ?, ?, ?)
==> Parameters: 批量学生A(String), 20(Integer), 批量专业(String), 70.00(BigDecimal)
==> Parameters: 批量学生B(String), 21(Integer), 批量专业(String), 75.00(BigDecimal)
==> Parameters: 批量学生C(String), 22(Integer), 批量专业(String), 80.00(BigDecimal)
【批量执行】待提交语句数: 1, 影响记录数: 3
【提交】批量插入已提交

========== openSession 方式 5:REUSE + 手动提交 ==========
【配置】ExecutorType=REUSE,复用 PreparedStatement
==>  Preparing: SELECT id, name, age, major, score FROM student WHERE id = ?
==> Parameters: 1(Integer)
<==        Row: 1, 大翔, 22, 计算机科学, 95.50
==> Parameters: 2(Integer)
<==        Row: 2, 白歌, 21, 软件工程, 88.00
==> Parameters: 3(Integer)
<==        Row: 3, 小崔, 20, 计算机科学, 92.00
复用查询结果: 大翔, 白歌, 小崔

========== 最终数据状态 ==========
当前共 10 条记录:
  Student{id=1, name='大翔', age=22, major='计算机科学', score=95.50}
  Student{id=2, name='白歌', age=21, major='软件工程', score=88.00}
  Student{id=3, name='小崔', age=20, major='计算机科学', score=92.00}
  Student{id=4, name='黄俪', age=21, major='信息安全', score=90.50}
  Student{id=5, name='李眉', age=22, major='软件工程', score=87.00}
  Student{id=6, name='自动提交学生', age=20, major='测试专业', score=80.00}
  Student{id=7, name='批量学生A', age=20, major='批量专业', score=70.00}
  Student{id=8, name='批量学生B', age=21, major='批量专业', score=75.00}
  Student{id=9, name='批量学生C', age=22, major='批量专业', score=80.00}

结果分析:

openSession 重载核心特性控制台 SQL 特征适用场景
openSession()autoCommit=false, SIMPLE单条 SQL 独立执行生产环境默认推荐,事务可控
openSession(true)autoCommit=true每条 DML 后立即提交简单查询、无事务要求的脚本
openSession(READ_COMMITTED)隔离级别显式指定与默认相同,但语义清晰需要防止脏读的金融/交易业务
openSession(BATCH)批量发送多条 INSERT 合并,一次网络往返大批量数据导入,性能提升显著
openSession(REUSE, false)复用 Statement相同 SQL 只 Prepare 一次同一会话内高频执行相同结构 SQL

重要观察:

  1. BATCH 模式下,insert 返回的 int 不是真实影响行数,而是批量队列中的位置序号,必须通过 flushStatements() 获取真实结果
  2. REUSE 模式下,相同 SQL 模板的 PreparedStatement 只编译一次,后续复用,减少数据库解析开销
  3. 自动提交模式下无法 rollback,一旦执行即持久化,生产环境 DML 业务严禁使用

易错场景 / 常见误区

误区错误代码/做法后果正解
Builder 长期持有将 SqlSessionFactoryBuilder 设为静态变量内存泄漏,配置无法热更新Builder 用完即丢,局部变量
多环境配置时未指定环境build(is) 默认环境在生产环境误连开发库生产事故,数据污染生产部署时显式 build(is, "production")
外部 Properties 未覆盖成功属性名与 XML 占位符不一致仍使用 XML 中的默认值确保 Properties 的 key 与 ${xxx} 中的 xxx 完全一致
自动提交模式下尝试 rollbackopenSession(true) 后调用 rollback()无效果,数据已持久化需要事务控制时,使用 openSession() 或 openSession(false)
BATCH 执行器查数据BATCH 模式下执行 selectOne可能返回不正确结果或未刷新数据BATCH 仅用于 insert/update/delete,查询用 SIMPLE
混淆 ExecutorType 与数据库批量认为 BATCH 会自动拼接 INSERT ... VALUES (),(),()MyBatis BATCH 是 JDBC 批量执行,非 SQL 拼接需要 SQL 拼接批量时,使用 <foreach> 或数据库特定语法
隔离级别设置无效在 openSession(level) 后手动改 Connection 隔离级别两者冲突,行为不可预期统一通过 openSession(level) 设置,不再手动修改
外部 Connection 未关导致泄漏openSession(conn) 后只关 Session 不关 Connection外部连接未归还,连接池耗尽外部连接的生命周期由提供方管理,MyBatis 不关闭外部连接

面试考点

Q1:SqlSessionFactoryBuilder 为什么设计为"用过即丢"?长期持有有什么问题?

A: SqlSessionFactoryBuilder 的唯一职责是解析配置并创建 SqlSessionFactory。一旦 Factory 创建完成,Builder 内部持有的 XML 解析器、临时对象等再无存在价值。长期持有会浪费内存,且当需要重新加载配置(如热更新)时,旧的 Builder 无法重新解析新的配置流。正确做法是将 Builder 作为方法级局部变量,build() 后立即释放引用。

Q2:openSession() 和 openSession(true) 有什么区别?生产环境推荐哪种?

A: openSession() 默认 autoCommit=false,DML 操作后必须手动调用 session.commit(),适合需要事务原子性的业务场景。openSession(true) 开启自动提交,每条 SQL 执行后立即提交,无法 rollback,失去了事务控制能力。生产环境强烈推荐 openSession()(手动提交模式),由业务层控制事务边界,确保数据一致性。自动提交模式仅适用于纯查询脚本或无事务要求的工具类程序。

Q3:MyBatis 的三种 ExecutorType 有什么区别?BATCH 执行器有什么注意事项?

A: SIMPLE(默认)为每条语句创建新的 PreparedStatement,执行后关闭,通用但无优化。REUSE 复用 PreparedStatement,缓存在 Map 中,适合同一会话内重复执行相同结构的 SQL,减少编译开销。BATCH 将多条修改语句缓存在队列中,通过 flushStatements() 一次性批量发送给数据库,大幅减少网络往返,适合大批量插入/更新。注意事项:BATCH 模式下 insert/update/delete 返回的 int 是队列序号而非真实影响行数;BATCH 模式下执行 select 可能返回不正确结果;必须在操作结束后调用 flushStatements() 和 commit()。

Q4:TransactionIsolationLevel 与 JDBC Connection 的隔离级别是什么关系?MyBatis 如何设置?

A: MyBatis 的 TransactionIsolationLevel 枚举是对 JDBC Connection 隔离级别常量的封装,两者一一对应:NONE→TRANSACTION_NONE,READ_UNCOMMITTED→TRANSACTION_READ_UNCOMMITTED,READ_COMMITTED→TRANSACTION_READ_COMMITTED,REPEATABLE_READ→TRANSACTION_REPEATABLE_READ,SERIALIZABLE→TRANSACTION_SERIALIZABLE。MyBatis 通过 openSession(TransactionIsolationLevel) 在创建 Connection 时立即设置隔离级别,确保整个 Session 生命周期内隔离级别一致。开发者不应在获取 Session 后再手动修改 Connection 的隔离级别,否则会导致行为不可预期。


小结

本节深入讲解了 SqlSessionFactoryBuilder 的 5 个 build() 重载和 SqlSessionFactory 的 8 个 openSession() 重载。通过多环境切换示例,我们理解了 build(is, env) 在开发/测试/生产部署中的价值;通过属性覆盖示例,我们掌握了密码外部化的安全实践;通过 openSession() 的各种重载实验,我们明确了自动提交、隔离级别、执行器类型对会话行为的影响。

关键记忆点:

  • SqlSessionFactoryBuilder = 方法级局部变量,用完即丢,严禁长期持有
  • build() 的外部 Properties 会覆盖 XML 中同名的 ${} 占位符,优先级:外部 Props > XML 内 <properties> > XML 默认值
  • openSession() 默认手动提交 + SIMPLE 执行器,是生产环境的标准选择
  • BATCH 执行器适合大批量 DML,但查询可能异常,且返回值为队列序号
  • REUSE 执行器复用 PreparedStatement,适合同结构 SQL 高频执行
  • TransactionIsolationLevel 在 openSession() 时一次性设置,Session 内保持一致
  • 外部 Connection 传入时,MyBatis 不管理其关闭,生命周期由提供方负责

下一章引子

现在我们已经掌握了 SqlSessionFactory 的构建和 SqlSession 的打开方式,也知道了如何控制事务和执行器。但 SqlSession 的 API 直接写字符串语句 ID(如 "com.fly.mapper.StudentMapper.findById")存在类型不安全、IDE 无法补全、重构困难等问题。MyBatis 提供的 getMapper(Class<T>) 可以生成 Mapper 接口的代理对象,让我们以 mapper.findById(1) 这种优雅的方式操作数据库。下一节将深入讲解 Mapper 接口与映射方式,对比 XML 映射与注解映射的语法差异、适用场景和底层代理原理。

上一页
第一个MyBatis程序
下一页
SqlSessionFactory与SqlSession