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

    • 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执行器类型
    • 分页插件

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&amp;serverTimezone=UTC&amp;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&amp;serverTimezone=UTC&amp;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 中 & 符号必须转义为 &amp;,否则解析报错
  • 每个 environment 的 id 必须唯一,否则后解析的会覆盖先解析的

易错场景/常见误区

误区错误表现正解
default 指向不存在的 id启动报 Environment with id 'xxx' not found确保 default 的值与某个 environment 的 id 精确匹配(区分大小写)
在 XML 的 URL 中直接使用 &解析 XML 时报 The reference to entity "serverTimezone" must end with ';' delimiterXML 中的 & 必须转义为 &amp;
认为多个 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 自管理事务与容器托管事务。

上一页
plugins
下一页
transactionManager