分布式配置刷新:dromara/mybatis-jpa-extra与Apollo配置中心
你是否还在为分布式环境下MyBatis配置修改需要重启服务而烦恼?是否遇到过加密密钥轮换导致系统不可用的尴尬?本文将详细介绍如何通过Apollo配置中心实现dromara/mybatis-jpa-extra的配置动态刷新,无需重启服务即可完成数据源切换、加密策略调整等关键配置更新。读完本文你将掌握:
- mybatis-jpa-extra配置体系与Apollo集成方案
- 实现加密密钥、分页参数等核心配置的动态刷新
- 分布式环境下配置变更的实时推送与应用
- 生产级配置刷新的最佳实践与避坑指南
项目概述与痛点分析
dromara/mybatis-jpa-extra是一款致力于简化MyBatis CUID操作、增强SELECT分页查询能力的开源框架。项目核心模块结构如下:
在分布式系统中,传统配置方式存在三大痛点:
- 配置修改需重启:数据库方言、加密密钥等修改需要重启应用
- 配置不一致:多实例部署时易出现配置同步延迟
- 密钥管理风险:加密密钥硬编码导致泄露风险
Apollo配置中心作为携程开源的分布式配置管理平台,提供了配置集中管理、实时推送、灰度发布等能力,与mybatis-jpa-extra结合可完美解决上述问题。
集成准备与环境配置
环境要求
| 组件 | 版本要求 | 备注 |
|---|---|---|
| JDK | 1.8+ | 官方文档 |
| Spring Boot | 2.3.x+ | spring-boot-starter |
| Apollo Client | 1.7.0+ | 国内CDN |
| MyBatis | 3.5.x+ | 核心依赖 |
依赖引入
在pom.xml中添加Apollo客户端依赖:
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
核心配置动态刷新实现
配置属性绑定分析
mybatis-jpa-extra的配置属性通过MybatisProperties类进行管理,关键配置项包括:
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
// 数据库方言配置
private String dialect;
// 加密开关
private boolean tableColumnEscape;
// 加密字符
private String tableColumnEscapeChar;
// 分表策略
private String tableColumnCase;
// 雪花算法参数
private int tableColumnSnowflakeDatacenterId;
private int tableColumnSnowflakeMachineId;
// ...其他配置
}
这些配置在应用启动时通过MybatisAutoConfiguration类初始化到系统常量中:
// 设置数据库方言
if (StringUtils.hasLength(this.properties.getDialect())) {
factory.setDialect(this.properties.getDialect());
}
// 初始化雪花算法
if(this.properties.getTableColumnSnowflakeDatacenterId()>0 &&
this.properties.getTableColumnSnowflakeMachineId()>0) {
IdentifierGeneratorFactory identifierGeneratorFactory = new IdentifierGeneratorFactory(
this.properties.getTableColumnSnowflakeDatacenterId(),
this.properties.getTableColumnSnowflakeMachineId()
);
MapperMetadata.setIdentifierGeneratorFactory(identifierGeneratorFactory);
}
Apollo配置中心设置
- 在Apollo控制台创建应用
MYBATIS-JPA-EXTRA - 创建命名空间
application并添加配置项:
| 配置Key | 示例值 | 说明 |
|---|---|---|
| mybatis.dialect | MySQLDialect | 数据库方言 |
| mybatis.table-column-escape | true | 启用字段转义 |
| mybatis.table-column-escape-char | ` | 转义字符 |
| mybatis.table-column-case | lowercase | 字段大小写策略 |
| mybatis.table-column-snowflake-datacenter-id | 1 | 雪花算法数据中心ID |
| mybatis.table-column-snowflake-machine-id | 1 | 雪花算法机器ID |
动态刷新实现方案
方案一:基于@RefreshScope的配置刷新
创建可刷新的配置包装类:
@Configuration
@RefreshScope
public class RefreshableMybatisConfig {
@Autowired
private MybatisProperties properties;
@Autowired(required = false)
private SqlSessionFactory sqlSessionFactory;
@PostConstruct
public void init() {
refreshMybatisConfig();
}
@RefreshScope
public void refreshMybatisConfig() {
// 刷新数据库方言
if (sqlSessionFactory != null && sqlSessionFactory.getConfiguration() != null) {
Configuration configuration = sqlSessionFactory.getConfiguration();
// 动态更新方言配置
if (StringUtils.hasLength(properties.getDialect())) {
try {
Class<?> dialectClass = Class.forName("org.dromara.mybatis.jpa.dialect." + properties.getDialect());
configuration.setDatabaseId(properties.getDialect());
// 更新方言实例
Field dialectField = configuration.getClass().getDeclaredField("dialect");
dialectField.setAccessible(true);
dialectField.set(configuration, dialectClass.newInstance());
} catch (Exception e) {
log.error("Failed to refresh dialect configuration", e);
}
}
// 刷新雪花算法配置
if(properties.getTableColumnSnowflakeDatacenterId()>0 &&
properties.getTableColumnSnowflakeMachineId()>0) {
IdentifierGeneratorFactory identifierGeneratorFactory = new IdentifierGeneratorFactory(
properties.getTableColumnSnowflakeDatacenterId(),
properties.getTableColumnSnowflakeMachineId()
);
MapperMetadata.setIdentifierGeneratorFactory(identifierGeneratorFactory);
}
// 更新其他配置项...
}
}
}
方案二:Apollo配置监听回调
实现Apollo的ConfigChangeListener接口:
@Component
public class MybatisConfigChangeListener implements ApplicationContextAware {
private ApplicationContext applicationContext;
@ApolloConfig
private Config config;
@PostConstruct
public void init() {
config.addChangeListener(changeEvent -> {
Set<String> changedKeys = changeEvent.changedKeys();
if (changedKeys.stream().anyMatch(k -> k.startsWith("mybatis."))) {
refreshMybatisConfiguration();
}
});
}
private void refreshMybatisConfiguration() {
// 获取MybatisProperties的Bean并刷新
MybatisProperties properties = applicationContext.getBean(MybatisProperties.class);
// 重新应用配置
MyBatisJpaSessionFactoryBean factory = applicationContext.getBean(MyBatisJpaSessionFactoryBean.class);
// ...重新设置配置逻辑
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
加密密钥动态更新实现
mybatis-jpa-extra的加密功能由crypto模块实现,支持AES、DES等多种加密算法:
public class EncryptFactory {
private static SymmetricEncrypt symmetricEncrypt;
public static SymmetricEncrypt getEncrypt() {
if (symmetricEncrypt == null) {
// 默认AES加密
symmetricEncrypt = new AesEncrypt();
}
return symmetricEncrypt;
}
// 动态设置加密密钥
public static void setEncryptKey(String key) {
getEncrypt().setKey(key);
}
}
结合Apollo实现密钥动态更新:
@Configuration
public class EncryptKeyRefreshConfig {
@ApolloConfig
private Config config;
@PostConstruct
public void init() {
// 初始加载密钥
String encryptKey = config.getProperty("mybatis.encrypt.key", "defaultKey123456");
EncryptFactory.getEncrypt().setKey(encryptKey);
// 监听密钥变化
config.addChangeListener(changeEvent -> {
if (changeEvent.changedKeys().contains("mybatis.encrypt.key")) {
String newKey = config.getProperty("mybatis.encrypt.key", "defaultKey123456");
EncryptFactory.getEncrypt().setKey(newKey);
log.info("Encrypt key has been refreshed");
}
});
}
}
配置刷新流程与原理
配置刷新的完整流程如下:
关键技术点:
- Spring Cloud Config的@RefreshScope:通过动态代理实现Bean的重新初始化
- Apollo的配置监听机制:实时接收配置变更事件
- MyBatis Configuration动态修改:通过反射更新私有字段
- 雪花算法工厂重置:确保新生成ID使用最新配置
测试验证与最佳实践
测试步骤
-
基础测试:
@SpringBootTest public class ConfigRefreshTest { @Autowired private MybatisProperties properties; @Autowired private SqlSessionFactory sqlSessionFactory; @Test public void testDialectRefresh() { // 初始配置验证 assertEquals("MySQLDialect", properties.getDialect()); // 模拟Apollo配置变更 ConfigService.getAppConfig().setProperty("mybatis.dialect", "PostgreSQLDialect"); // 等待配置刷新 Thread.sleep(1000); // 验证配置已更新 assertEquals("PostgreSQLDialect", properties.getDialect()); assertEquals("PostgreSQLDialect", sqlSessionFactory.getConfiguration().getDatabaseId()); } } -
集成测试:使用Apollo的
ConfigService模拟配置推送,验证分页查询、加密功能是否按新配置工作
最佳实践
-
配置分组管理:
- 将静态配置与动态配置分离
- 敏感配置(如加密密钥)使用Apollo的加密功能存储
-
灰度发布策略:
@ApolloConfig private Config config; public void refreshMybatisConfig() { // 获取当前实例ID String instanceId = environment.getProperty("instance.id", "default"); // 获取灰度配置 String grayDialect = config.getProperty("mybatis.dialect.gray." + instanceId, ""); if (StringUtils.hasLength(grayDialect)) { // 应用灰度配置 updateDialect(grayDialect); } else { // 应用默认配置 updateDialect(config.getProperty("mybatis.dialect", "MySQLDialect")); } } -
配置变更审计:
@Component public class ConfigChangeAuditor implements ConfigChangeListener { @Override public void onChange(ConfigChangeEvent changeEvent) { for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); log.info("Config changed - Key: {}, OldValue: {}, NewValue: {}, ChangeType: {}", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType()); // 记录到审计系统... } } }
总结与展望
通过Apollo配置中心与dromara/mybatis-jpa-extra的集成,我们实现了核心配置的动态刷新,解决了分布式环境下配置管理的痛点。关键收获包括:
- 配置体系:理解了MybatisProperties与MybatisAutoConfiguration构成的配置体系
- 动态刷新:掌握了基于@RefreshScope和Apollo监听的两种刷新方案
- 安全实践:实现了加密密钥的动态更新与安全管理
未来展望:
- mybatis-jpa-extra官方可能会内置配置刷新能力
- 结合Nacos等其他配置中心的适配方案
- 配置变更的预验证与自动回滚机制
掌握分布式配置刷新技术,不仅能提升系统的可用性和安全性,更能为微服务架构下的配置管理提供标准化解决方案。建议结合项目实际需求选择合适的刷新策略,并严格遵循配置变更的灰度发布与审计规范。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考




