1. MyBatis配置中的properties元素解析
第一次在项目中看到MyBatis配置里那个<properties>标签时,我以为是用来定义SQL语句的某种特殊属性。直到某天需要同时对接开发/测试/生产三套环境时,才真正理解这个看似简单的配置项为何被称为MyBatis的"环境切换神器"。这个标签本质上解决的是配置参数动态化的问题——让同一套MyBatis配置能够根据运行环境自动切换数据库连接、文件路径等变量值。
举个例子,我们团队曾遇到过这样的情况:本地开发用本机MySQL,测试环境用10.0.0.1:3306,生产环境用阿里云RDS。如果硬编码这些连接信息,每次发布都要手动修改xml文件,不仅容易出错,还可能引发严重事故。而使用properties元素配合资源文件,只需在打包时指定不同环境的配置,就能自动完成参数替换。这种机制与Spring的Profile有异曲同工之妙,但实现更加轻量级。
2. properties的三种配置方式详解
2.1 内联properties定义
最简单的使用方式是在mybatis-config.xml中直接定义键值对:
xml复制<properties>
<property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="jdbc.url" value="jdbc:mysql://localhost:3306/dev_db"/>
</properties>
这种写法的优点是直观明了,适合少量固定不变的配置。但实际项目中我几乎不会这样用——当需要修改配置时,你得重新编译打包整个项目,这违反了配置与代码分离的原则。
2.2 外部properties文件引用
更专业的做法是将配置外置到.properties文件中:
xml复制<properties resource="config/jdbc.properties"/>
对应的jdbc.properties文件内容:
properties复制jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://10.0.0.1:3306/test_db
jdbc.username=test_user
jdbc.password=test_123
这种方式的优势在于:
- 不同环境可以维护不同的properties文件
- 敏感信息不会暴露在代码仓库中
- 修改配置无需重新编译项目
我在实际项目中会建立如下目录结构:
code复制src/main/resources
├── config
│ ├── dev
│ │ └── jdbc.properties
│ ├── test
│ │ └── jdbc.properties
│ └── prod
│ └── jdbc.properties
└── mybatis-config.xml
然后通过Maven的profile机制在打包时选择对应的配置文件。
2.3 程序动态注入properties
最高级的用法是通过SqlSessionFactoryBuilder传入Properties对象:
java复制Properties props = new Properties();
props.load(new FileInputStream("config/jdbc.properties"));
SqlSessionFactory factory = new SqlSessionFactoryBuilder()
.build(reader, props);
这种方式特别适合需要从非标准位置加载配置的场景,比如:
- 从加密的配置文件中读取
- 从远程配置中心获取
- 运行时动态生成的配置
我曾在一个金融项目中采用这种方案,配合自研的配置管理系统,实现了数据库密码的自动轮换。
3. properties的优先级与覆盖规则
当多种配置方式混用时,MyBatis按照以下顺序加载properties:
- 首先读取
<properties>标签内定义的属性 - 然后加载resource/url指定的外部文件属性(重复key会覆盖)
- 最后处理程序传入的Properties对象(最高优先级)
这个顺序经常让人困惑。有次我在排查问题时,发现配置没生效,就是因为没注意到程序传入的参数会覆盖文件中的配置。正确的理解应该是:后加载的配置会覆盖先前的同名key。
可以通过明确指定"override"属性来控制是否允许覆盖:
xml复制<properties resource="config/base.properties"/>
<properties resource="config/env/override.properties" override="false"/>
当override="false"时,即使override.properties中有同名key,也不会覆盖base.properties中的值。
4. properties在配置中的实际应用
4.1 数据源配置参数化
最典型的应用场景是配置数据源:
xml复制<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
这样只需修改properties文件,就能切换不同环境的数据库连接。
4.2 SQL映射文件中的变量替换
properties还可以在mapper.xml中使用:
xml复制<select id="getUser" resultType="User">
SELECT * FROM ${table.prefix}_user WHERE id = #{id}
</select>
配合不同环境的properties文件:
properties复制# 开发环境
table.prefix=dev
# 生产环境
table.prefix=prod
这种动态表名的技巧在分库分表场景特别有用。
4.3 类型处理器参数配置
自定义类型处理器也可以通过properties接收参数:
xml复制<typeHandlers>
<typeHandler handler="com.example.CustomTypeHandler"
javaType="java.util.Date"
jdbcType="TIMESTAMP"
format="${date.format}"/>
</typeHandlers>
然后在properties中定义:
properties复制date.format=yyyy-MM-dd HH:mm:ss
5. 高级技巧与避坑指南
5.1 属性值的动态解析
MyBatis支持在属性值中引用其他属性:
properties复制jdbc.host=10.0.0.1
jdbc.port=3306
jdbc.url=jdbc:mysql://${jdbc.host}:${jdbc.port}/db
这个特性在组合复杂配置时非常有用,但要注意避免循环引用。
5.2 默认值设置技巧
从MyBatis 3.4.2开始,支持为属性设置默认值:
xml复制<property name="jdbc.timeout" value="${jdbc.timeout:30}"/>
当jdbc.timeout未定义时,会使用默认值30。这个语法与Spring的属性占位符保持一致。
5.3 常见问题排查
-
属性未生效:
- 检查文件路径是否正确(resource是类路径,url是文件系统路径)
- 确认没有更高优先级的配置覆盖了当前值
- 查看MyBatis启动日志中的属性加载记录
-
特殊字符处理:
properties复制# 包含冒号的值需要引号包裹 jdbc.url="jdbc:mysql://localhost:3306/db" # 包含美元符号需要转义 encryption.key=\${must.not.be.interpolated} -
性能优化:
- 避免在高频执行的SQL中使用大量${}替换,这会影响解析性能
- 对于不变的配置,考虑在应用启动时一次性解析并缓存
6. 与Spring集成的注意事项
当MyBatis与Spring整合时,properties的加载方式会有一些变化:
6.1 通过Spring注入properties
推荐的方式是让Spring管理配置属性:
xml复制<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configurationProperties">
<props>
<prop key="table.prefix">dev_</prop>
</props>
</property>
</bean>
这样可以利用Spring强大的属性管理功能,包括:
- 多种属性源支持(环境变量、系统属性等)
- 属性占位符解析器
- 属性加密等高级特性
6.2 避免配置冲突
常见的问题是MyBatis和Spring都尝试加载相同的properties文件,导致配置混乱。建议:
- 统一由Spring管理属性源
- 在mybatis-config.xml中只保留必要的MyBatis特有配置
- 通过
@Value注解将属性注入到Mapper接口中
我在实际项目中最喜欢的做法是创建一个专门的配置类:
java复制@Configuration
@PropertySource("classpath:config/jdbc.properties")
public class MyBatisConfig {
@Value("${jdbc.url}")
private String jdbcUrl;
// 其他属性...
@Bean
public SqlSessionFactory sqlSessionFactory() {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
Properties props = new Properties();
props.setProperty("jdbc.url", jdbcUrl);
factory.setConfigurationProperties(props);
return factory.getObject();
}
}
7. 最佳实践总结
经过多个项目的实践验证,我总结出以下properties使用规范:
-
环境分离原则:
- 为每个环境维护独立的properties文件
- 使用Maven profile或Spring profile实现环境切换
- 敏感配置与普通配置分开管理
-
安全规范:
- 不要将生产环境的密码提交到代码仓库
- 考虑使用Jasypt等工具加密敏感属性
- 设置适当的文件权限
-
性能建议:
- 避免在循环或高频调用中使用${}替换
- 对于复杂配置,考虑在应用启动时预解析
- 使用缓存减少配置解析开销
-
维护性技巧:
- 为属性添加清晰的注释说明
- 使用统一的命名规范(如jdbc.*前缀)
- 定期清理不再使用的属性
一个典型的项目配置结构可能如下:
code复制config/
├── application.properties # 公共基础配置
├── env/
│ ├── dev.properties # 开发环境特有配置
│ ├── test.properties # 测试环境配置
│ └── prod.properties # 生产环境配置
└── sensitive/
├── dev.encrypted.properties # 开发环境敏感配置(加密)
└── prod.encrypted.properties
最后分享一个真实案例:在一次紧急故障处理中,我们通过动态修改properties文件中的数据库连接字符串,实现了生产环境数据库的快速切换,整个过程无需重启应用。这种灵活性正是properties设计价值的完美体现。