settings
导学
本节学习目标:
- 理解
settings是 MyBatis 全局行为控制的"总控面板" - 掌握缓存、懒加载、驼峰映射、自动生成主键等核心开关的默认值与适用场景
- 能够根据项目需求合理调整
settings,避免性能陷阱与行为偏差 - 了解
settings在 MyBatis 初始化阶段的解析与生效机制
定义
MyBatis 的默认行为并非适用于所有项目。例如:
- 有些项目希望数据库下划线字段自动映射到 Java 驼峰属性,省去写
resultMap的繁琐 - 有些项目需要开启二级缓存提升读性能
- 有些项目依赖 MySQL 自增主键,希望插入后自动回填 ID 到实体对象
settings 元素正是为了解决这些全局行为差异化需求而存在的。它包含数十个开关项,每一项都深刻影响 MyBatis 的运行时表现。
适用位置与核心属性
settings 位于 mybatis-config.xml 中 properties 之后、typeAliases 之前。
核心结构:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="false"/>
<!-- 更多 setting... -->
</settings>
| 属性名 | 默认值 | 说明 |
|---|---|---|
name | 必填 | setting 的名称,必须是 MyBatis 支持的合法 key |
value | 必填 | 对应 setting 的取值,类型由具体 setting 决定(boolean/string/enum) |
核心原理
MyBatis 初始化配置解析流程
关键源码逻辑:XMLConfigBuilder.settingsElement(Properties props) 方法会遍历所有 setting 标签,通过 Configuration 类中对应的 setter 方法将值注入。例如 cacheEnabled 对应 Configuration.setCacheEnabled(boolean)。这些值一旦在 SqlSessionFactory 创建时确定,后续整个应用生命周期内生效(除非重新构建 SqlSessionFactory)。
完整示例
场景说明
乐途公司员工管理系统使用 MySQL 5.7,表字段命名采用下划线风格(employee_name、create_time),而 Java 实体采用驼峰命名(employeeName、createTime)。同时系统需要:
- 开启二级缓存减少数据库压力
- 开启驼峰自动映射减少
resultMap编写 - 插入员工记录后自动获取 MySQL 自增主键
- 开启懒加载优化关联部门信息的查询性能
操作前的状态
Employee 实体类:
public class Employee {
private Integer employeeId;
private String employeeName;
private String departmentCode;
private Date createTime;
// getter / setter 省略
}
数据库表:
CREATE TABLE employee (
employee_id INT PRIMARY KEY AUTO_INCREMENT,
employee_name VARCHAR(50) NOT NULL,
department_code VARCHAR(20),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
完整配置代码
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"/>
<!-- 全局行为总控面板 -->
<settings>
<!-- 开启二级缓存,提升查询性能 -->
<setting name="cacheEnabled" value="true"/>
<!-- 开启懒加载,关联对象按需查询 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭激进懒加载,避免触发不必要的关联查询 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启驼峰映射:employee_name -> employeeName -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 插入后自动获取数据库生成的主键 -->
<setting name="useGeneratedKeys" value="true"/>
<!-- 指定默认执行器为 SIMPLE -->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!-- 自动映射行为:对未明确映射的字段做部分自动映射 -->
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!-- 指定日志实现为 SLF4J -->
<setting name="logImpl" value="SLF4J"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/EmployeeMapper.xml"/>
</mappers>
</configuration>
EmployeeMapper.xml(简化演示):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.feixiang.mapper.EmployeeMapper">
<!-- 由于 mapUnderscoreToCamelCase=true,无需 resultMap 即可自动映射 -->
<select id="selectById" resultType="com.feixiang.entity.Employee">
SELECT employee_id, employee_name, department_code, create_time
FROM employee
WHERE employee_id = #{id}
</select>
<!-- useGeneratedKeys=true 生效,插入后 employeeId 会被回填 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="employeeId">
INSERT INTO employee (employee_name, department_code)
VALUES (#{employeeName}, #{departmentCode})
</insert>
</mapper>
Java 测试代码:
public class SettingsDemo {
public static void main(String[] args) throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
try (SqlSession session = factory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
// 测试插入并回填主键
Employee emp = new Employee();
emp.setEmployeeName("张三");
emp.setDepartmentCode("DEV001");
mapper.insert(emp);
session.commit();
System.out.println("插入后自动回填的主键: " + emp.getEmployeeId());
// 测试驼峰自动映射查询
Employee result = mapper.selectById(emp.getEmployeeId());
System.out.println("查询结果: " + result.getEmployeeName()
+ ", 创建时间: " + result.getCreateTime());
}
}
}
实际效果/结果
控制台输出:
插入后自动回填的主键: 1
查询结果: 张三, 创建时间: 2024-01-15 09:30:00
效果验证:
employee_id自动映射到employeeId,无需resultMap- 插入后
employeeId从null变为1,说明useGeneratedKeys生效 - 若关闭
mapUnderscoreToCamelCase,employeeName和createTime将为null
易错场景/常见误区
| 误区 | 错误表现 | 正解 |
|---|---|---|
认为 useGeneratedKeys=true 全局生效后所有插入都自动回填 | 对非自增主键表(如 UUID 主键)插入后对象 ID 仍为 null | useGeneratedKeys 仅对支持自增的数据库(MySQL、SQL Server)有效;Oracle 序列需用 selectKey |
开启 lazyLoadingEnabled 但不关闭 aggressiveLazyLoading | 只要访问对象任意方法就触发所有懒加载属性查询,性能更差 | 两者同时配置:lazyLoadingEnabled=true + aggressiveLazyLoading=false |
mapUnderscoreToCamelCase=true 后仍写冗长 resultMap | 代码冗余,维护成本增加 | 简单场景直接依赖自动映射,复杂场景(字段别名、类型转换)再用 resultMap |
随意修改 defaultExecutorType 为 BATCH | 普通查询也走批处理执行器,导致查询结果异常或事务行为不符合预期 | BATCH 仅用于批量插入/更新场景,且应在 openSession(ExecutorType.BATCH) 时临时指定 |
cacheEnabled=true 但 Mapper XML 中未加 <cache/> | 二级缓存并未真正启用,只是开放了开关 | 全局开关 cacheEnabled 是必要条件,还需在每个 Mapper 中声明 <cache/> 或 @CacheNamespace |
面试考点
Q1:mapUnderscoreToCamelCase 开启后,MyBatis 是如何实现下划线到驼峰的转换的?
A:MyBatis 在
DefaultObjectWrapper和MetaObject处理属性映射时,会调用PropertyTokenizer和MapWrapper。开启该设置后,MyBatis 在查找目标属性时,会将下划线字段名转换为驼峰形式进行匹配,例如employee_name会尝试匹配employeeName。这一转换发生在ResultSetHandler处理结果集的阶段。
Q2:lazyLoadingEnabled 和 aggressiveLazyLoading 有什么区别?
A:
lazyLoadingEnabled控制是否开启懒加载(即关联对象是否在首次访问时才查询)。aggressiveLazyLoading控制懒加载的"激进程度":当为true时,只要调用对象的任意方法(包括toString、equals、hashCode),就会触发所有懒加载属性;当为false时,只有真正访问该属性时才触发查询。生产环境推荐true+false的组合。
Q3:为什么 settings 一旦设置后,整个应用生命周期都生效?
A:因为
settings的解析发生在SqlSessionFactory构建阶段,所有值被写入Configuration对象。SqlSessionFactory是线程安全的单例,其内部的Configuration不会被修改。后续每次openSession()都复用同一份Configuration,因此settings具有全局性和不可变性。
Q4:autoMappingBehavior 的三个取值 NONE、PARTIAL、FULL 有什么区别?
A:
NONE表示完全关闭自动映射,所有字段必须通过resultMap显式配置;PARTIAL(默认)表示对未嵌套的结果对象自动映射简单属性,但嵌套对象不自动映射;FULL表示对所有结果对象(包括嵌套对象)都进行自动映射。推荐默认PARTIAL,在简化代码的同时避免嵌套对象映射混乱。
小结
settings 是 MyBatis 全局配置中最具"影响力"的元素,它决定了缓存、懒加载、命名映射、主键生成、日志实现等核心行为。理解每个开关的默认值、生效时机和适用边界,是避免"配置一行,排查半天"的关键。建议团队在项目初期就统一 settings 模板,并在代码评审中作为必检项。
下一章引子
配置好全局行为后,Mapper XML 中频繁出现的全限定类名(如 com.feixiang.entity.Employee)显得冗长且易错。MyBatis 提供了 typeAliases 机制,允许为 Java 类型注册简短别名,让 XML 更加简洁可读。接下来,我们将学习别名的注册方式与内置别名清单。