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 原始写法中,开发者面对以下困境:
- 环境切换困难:开发库、测试库、生产库的连接信息散落在代码各处,切换环境需要改多处代码
- 事务控制僵化:
Connection的自动提交和隔离级别一旦设置,整个应用难以按需调整 - 批量操作低效:大量插入时,每条 SQL 单独发送网络请求,性能极差
- 外部连接复用:与 Spring 等框架集成时,需要让 MyBatis 使用框架管理的数据库连接
MyBatis 通过 build() 的多重载支持配置源多样化和属性动态覆盖,通过 openSession() 的多重载支持会话行为精细化控制。
核心原理
build() 与 openSession() 完整流程图
流程图解读:
- 构建阶段:
build()方法首先解析 XML 或接收现成的Configuration,生成全局配置对象,再封装为SqlSessionFactory。外部传入的Properties会覆盖 XML 中同名的占位符变量(如${jdbc.url})。 - 环境隔离:XML 中可配置多个
<environment>,build(is, env)通过id切换不同数据库连接,实现开发/测试/生产环境一键切换。 - 会话阶段:
openSession()根据参数组合创建不同特性的Executor,最终包装为SqlSession。ExecutorType决定了 SQL 语句的预编译和复用策略。 - 外部连接:
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 |
重要观察:
BATCH模式下,insert返回的int不是真实影响行数,而是批量队列中的位置序号,必须通过flushStatements()获取真实结果REUSE模式下,相同 SQL 模板的PreparedStatement只编译一次,后续复用,减少数据库解析开销- 自动提交模式下无法
rollback,一旦执行即持久化,生产环境 DML 业务严禁使用
易错场景 / 常见误区
| 误区 | 错误代码/做法 | 后果 | 正解 |
|---|---|---|---|
| Builder 长期持有 | 将 SqlSessionFactoryBuilder 设为静态变量 | 内存泄漏,配置无法热更新 | Builder 用完即丢,局部变量 |
| 多环境配置时未指定环境 | build(is) 默认环境在生产环境误连开发库 | 生产事故,数据污染 | 生产部署时显式 build(is, "production") |
| 外部 Properties 未覆盖成功 | 属性名与 XML 占位符不一致 | 仍使用 XML 中的默认值 | 确保 Properties 的 key 与 ${xxx} 中的 xxx 完全一致 |
| 自动提交模式下尝试 rollback | openSession(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枚举是对 JDBCConnection隔离级别常量的封装,两者一一对应: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 映射与注解映射的语法差异、适用场景和底层代理原理。