environments
导学
本节学习目标:
- 理解
environments是 MyBatis 多数据库环境管理的容器 - 掌握
environment子元素的配置结构与default属性的作用 - 能够在项目中配置开发、测试、生产多套环境并正确切换
- 了解
environments在 MyBatis 初始化时的解析与选择机制
定义
在实际项目生命周期中,应用需要连接不同的数据库:
- 开发环境:本地 MySQL 5.7,方便调试
- 测试环境:测试服务器上的独立数据库,数据隔离
- 生产环境:线上高可用数据库集群,连接参数严格保密
如果为每个环境维护一份独立的 mybatis-config.xml,不仅维护成本高,还容易因配置不同步导致"开发正常、线上报错"的问题。environments 元素的核心使命是在一套配置文件中定义多套数据库环境,并通过 default 属性指定当前生效的环境,实现环境切换的配置化与标准化。
适用位置与核心属性
environments 位于 mybatis-config.xml 中 plugins 之后、databaseIdProvider 之前。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 开发环境连接配置 -->
</dataSource>
</environment>
<environment id="production">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 生产环境连接配置 -->
</dataSource>
</environment>
</environments>
| 属性 | 是否必填 | 说明 |
|---|---|---|
default | 必填 | 指定默认使用的环境 id。当 Java 代码未显式指定环境时,MyBatis 使用此环境 |
environment 子元素属性
| 属性 | 是否必填 | 说明 |
|---|---|---|
id | 必填 | 环境的唯一标识,用于区分不同环境。default 属性必须指向某个存在的 id |
每个 environment 内部必须包含且仅包含:
- 一个
transactionManager元素 - 一个
dataSource元素
核心原理
多环境切换流程图
源码层面:XMLConfigBuilder.environmentsElement(XNode) 方法负责解析 environments。它首先读取 default 属性,然后根据传入参数或默认值确定目标 environment,解析其内部的 transactionManager 和 dataSource,最终封装为 org.apache.ibatis.mapping.Environment 对象存入 Configuration。
Java 代码动态切换环境:
// 使用默认环境(由 XML 中 default 属性决定)
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 显式指定生产环境
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream, "production");
完整示例
场景说明
乐途公司员工管理系统需要同时维护三套环境:
development:开发环境,连接本地 MySQL 5.7 的employee_dev库test:测试环境,连接测试服务器的employee_test库production:生产环境,连接线上主从集群的employee_prod库
要求通过 default 属性默认使用开发环境,同时支持 Java 代码在集成测试时切换到测试环境。
操作前的状态
项目目录:
src/main/resources/
├── mybatis-config.xml
├── db-dev.properties
├── db-test.properties
└── db-prod.properties
完整配置代码
步骤一:准备三套外部属性文件
db-dev.properties:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/employee_dev?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8
username=root
password=dev123
db-test.properties:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.1.100:3306/employee_test?useSSL=true&serverTimezone=UTC&characterEncoding=UTF-8
username=test_user
password=test_secret
db-prod.properties:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://rm-bp1xxxx.mysql.rds.aliyuncs.com:3306/employee_prod?useSSL=true&serverTimezone=UTC&characterEncoding=UTF-8
username=prod_app
password=prod_secret_encrypted
步骤二:配置 environments
<?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-dev.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.feixiang.entity"/>
</typeAliases>
<!-- 多环境配置 -->
<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>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.1.100:3306/employee_test?useSSL=true&serverTimezone=UTC&characterEncoding=UTF-8"/>
<property name="username" value="test_user"/>
<property name="password" value="test_secret"/>
</dataSource>
</environment>
<environment id="production">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://rm-bp1xxxx.mysql.rds.aliyuncs.com:3306/employee_prod?useSSL=true&serverTimezone=UTC&characterEncoding=UTF-8"/>
<property name="username" value="prod_app"/>
<property name="password" value="prod_secret_encrypted"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/EmployeeMapper.xml"/>
</mappers>
</configuration>
步骤三:Java 代码动态切换环境
public class EnvironmentDemo {
public static void main(String[] args) throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 场景1:使用默认环境(development)
SqlSessionFactory devFactory = new SqlSessionFactoryBuilder().build(is);
System.out.println("默认环境创建成功");
// 场景2:显式切换到测试环境
is = Resources.getResourceAsStream("mybatis-config.xml"); // 重新加载流
SqlSessionFactory testFactory = new SqlSessionFactoryBuilder().build(is, "test");
System.out.println("测试环境创建成功");
// 场景3:显式切换到生产环境
is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory prodFactory = new SqlSessionFactoryBuilder().build(is, "production");
System.out.println("生产环境创建成功");
// 验证:使用测试环境执行查询
try (SqlSession session = testFactory.openSession()) {
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
Employee emp = mapper.selectById(1);
System.out.println("测试环境查询结果: " + (emp != null ? emp.getEmployeeName() : "无数据"));
}
}
}
实际效果/结果
控制台输出:
默认环境创建成功
测试环境创建成功
生产环境创建成功
测试环境查询结果: 王五
效果验证:
- 不传入环境参数时,使用
default="development"对应的配置 - 传入
"test"时,MyBatis 解析id="test"的环境,连接测试服务器数据库 - 若传入不存在的环境名(如
"staging"),启动时抛出:Exception: Environment with id 'staging' not found
分析
default属性是"兜底策略",确保 Java 代码未指定时仍有明确的环境可用- 生产环境的密码直接写在 XML 中存在安全隐患,实际项目中应结合
properties外部化或集成配置中心(如 Apollo、Nacos) - XML 中
&符号必须转义为&,否则解析报错 - 每个
environment的id必须唯一,否则后解析的会覆盖先解析的
易错场景/常见误区
| 误区 | 错误表现 | 正解 |
|---|---|---|
default 指向不存在的 id | 启动报 Environment with id 'xxx' not found | 确保 default 的值与某个 environment 的 id 精确匹配(区分大小写) |
在 XML 的 URL 中直接使用 & | 解析 XML 时报 The reference to entity "serverTimezone" must end with ';' delimiter | XML 中的 & 必须转义为 & |
| 认为多个 environment 可以同时生效 | 期望一个 SqlSessionFactory 同时连接多套数据库 | 一个 SqlSessionFactory 只对应一个 Environment。多数据源需要创建多个 SqlSessionFactory |
环境 id 重复 | 后定义的环境覆盖先定义的环境,导致连接了错误的数据库 | 确保所有 environment 的 id 全局唯一 |
试图在 environment 外定义 dataSource | 报 DTD 约束错误 | dataSource 必须是 environment 的直接子元素,不能独立存在 |
| 生产环境密码明文存储 | 安全隐患 | 使用 properties 外部化,或结合加密组件,或集成配置中心动态注入 |
面试考点
Q1:MyBatis 的 environments 中 default 属性有什么作用?
A:
default指定当 Java 代码通过SqlSessionFactoryBuilder.build(InputStream)未显式传入环境名时,MyBatis 默认使用的environment的id。它是多环境配置中的"兜底策略",确保应用始终有明确的数据库环境可用。
Q2:如何在 Java 代码中切换不同的 environment?
A:通过
SqlSessionFactoryBuilder.build(InputStream inputStream, String environment)的重载方法,第二个参数传入目标环境的id。例如build(is, "production")会使用id="production"的环境配置。若传入的id不存在,启动时会抛出异常。
Q3:一个 SqlSessionFactory 能否同时管理多个 environment?
A:不能。
SqlSessionFactory在构建时只会解析并绑定一个Environment对象到Configuration中。如果需要同时操作多个数据库,必须创建多个SqlSessionFactory实例。这也是 Spring 集成 MyBatis 时配置多数据源的基本原理。
小结
environments 是 MyBatis 应对多环境部署的标准方案。通过在一套 XML 中定义多套 environment,配合 default 属性和 Java 代码的动态传入,可以实现开发、测试、生产环境的无缝切换。记住:一个 SqlSessionFactory 只绑定一个环境,多数据源需要多工厂;XML 中的特殊字符(&)必须正确转义。
下一章引子
环境配置的核心在于事务管理和数据源管理。每个 environment 内部都必须包含一个 transactionManager 和一个 dataSource。接下来,我们将先深入 transactionManager,理解 MyBatis 的两种事务管理模式:JDBC 自管理事务与容器托管事务。