SpringBoot配置Mysql+达梦双数据源
'# SpringBoot配置Mysql+达梦双数据源
一、背景与问题
在分布式系统中,业务系统往往需要同时访问多个数据库。例如某金融系统可能需要同时连接MySQL和达梦数据库(DM),分别用于处理交易日志和核心业务数据。这种场景下,传统的单数据源配置无法满足需求,需要实现双数据源配置。
双数据源配置面临以下核心挑战:
- 如何动态切换数据源
- 如何处理跨数据源的事务
- 如何保证数据一致性
- 如何应对不同数据库的差异(如达梦数据库的特殊语法)
二、基本原理
SpringBoot的双数据源方案本质上是基于AbstractRoutingDataSource的多数据源抽象。其核心原理如下:
- 定义多个数据源实例(DataSource)
- 创建抽象数据源(AbstractRoutingDataSource),通过
determineCurrentLookupKey()方法决定使用哪个数据源 - 通过AOP或注解实现数据源切换逻辑
- 配合事务管理器处理分布式事务
达梦数据库作为国产数据库,其JDBC驱动与MySQL存在差异,需要特别处理连接参数和驱动类。
三、环境准备
1. 依赖配置(pom.xml)
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 达梦数据库驱动 -->
<dependency>
<groupId>com.dameng</groupId>
<artifactId>dmjdbc</artifactId>
<version>5.1.1</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>2. 数据库配置(application.yml)
spring:
datasource:
mysql:
url: jdbc:mysql://localhost:3306/mysql_db?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
dm:
url: jdbc:dm://localhost:5236/dm_db?characterEncoding=utf8
username: sysdba
password: sysdba
driver-class-name: dm.jdbc.driver.DmDriver四、核心实现
1. 自定义数据源路由类
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
CONTEXT.set(dataSource);
}
public static String getDataSource() {
return CONTEXT.get();
}
public static void clearDataSource() {
CONTEXT.remove();
}
}2. 配置双数据源
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.dm")
public DataSource dmDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(Map.of(
"mysql", mysqlDataSource(),
"dm", dmDataSource()
));
dynamicDataSource.setDefaultTargetDataSource(mysqlDataSource());
return dynamicDataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
}3. 使用注解切换数据源
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default "mysql";
}@Aspect
@Component
public class DataSourceAspect {
@Around("@annotation(dataSource)")
public Object around(ProceedingJoinPoint point, DataSource dataSource) throws Throwable {
String dataSourceName = dataSource.value();
DataSourceContextHolder.setDataSource(dataSourceName);
try {
return point.proceed();
} finally {
DataSourceContextHolder.clearDataSource();
}
}
}五、完整案例
1. 项目结构
src/
├── main/
│ ├── java/
│ │ └── com.example.demo/
│ │ ├── config/
│ │ │ └── DataSourceConfig.java
│ │ ├── aspect/
│ │ │ └── DataSourceAspect.java
│ │ ├── annotation/
│ │ │ └── DataSource.java
│ │ ├── service/
│ │ │ └── DualDataSourceService.java
│ │ └── controller/
│ │ └── DualDataSourceController.java
│ └── resources/
│ └── application.yml2. 完整案例代码
// DualDataSourceService.java
@Service
public class DualDataSourceService {
@Autowired
private JdbcTemplate mysqlJdbcTemplate;
@Autowired
private JdbcTemplate dmJdbcTemplate;
@DataSource("mysql")
public void saveToMysql(String data) {
mysqlJdbcTemplate.update("INSERT INTO test_table (content) VALUES (?)", data);
}
@DataSource("dm")
public void saveToDm(String data) {
dmJdbcTemplate.update("INSERT INTO test_table (content) VALUES (?)", data);
}
@DataSource("mysql")
public List<String> queryFromMysql() {
return mysqlJdbcTemplate.queryForList("SELECT content FROM test_table", String.class);
}
@DataSource("dm")
public List<String> queryFromDm() {
return dmJdbcTemplate.queryForList("SELECT content FROM test_table", String.class);
}
}// DualDataSourceController.java
@RestController
@RequestMapping("/dual")
public class DualDataSourceController {
@Autowired
private DualDataSourceService dualDataSourceService;
@PostMapping("/mysql")
public void saveToMysql(@RequestParam String data) {
dualDataSourceService.saveToMysql(data);
}
@PostMapping("/dm")
public void saveToDm(@RequestParam String data) {
dualDataSourceService.saveToDm(data);
}
@GetMapping("/mysql")
public List<String> queryFromMysql() {
return dualDataSourceService.queryFromMysql();
}
@GetMapping("/dm")
public List<String> queryFromDm() {
return dualDataSourceService.queryFromDm();
}
}六、源码解析
1. DynamicDataSource类
- 通过重写
determineCurrentLookupKey()方法实现数据源切换 - 使用ThreadLocal保证线程安全
- 支持动态切换数据源,适用于多租户、多环境等场景
2. 数据源配置
- 使用
@ConfigurationProperties自动绑定配置 - 配置两个独立的数据源实例
- 通过
AbstractRoutingDataSource实现多数据源抽象
3. 事务管理
- 配合
DataSourceTransactionManager处理事务 - 注意:跨数据源事务需要配置分布式事务管理器(如JTA)
七、进阶使用
1. 动态数据源策略
public class DynamicDataSourceStrategy implements RoutingStrategy {
@Override
public String determineCurrentLookupKey() {
// 根据业务逻辑动态选择数据源
return System.currentTimeMillis() % 2 == 0 ? "mysql" : "dm";
}
}2. 数据源监控
@Configuration
public class DataSourceMonitor {
@Bean
public DataSourceMonitorBean dataSourceMonitorBean(DataSource dynamicDataSource) {
return new DataSourceMonitorBean(dynamicDataSource);
}
}3. 优化建议
- 对频繁切换的数据源使用连接池
- 对不常用的数据源配置空闲超时
- 对敏感操作增加日志记录
八、性能与工程实践
1. 性能优化
| 优化项 | 建议 | 原因 |
|---|---|---|
| 连接池配置 | 设置maxPoolSize为100 | 防止资源耗尽 |
| SQL预编译 | 使用PreparedStatement | 防止SQL注入 |
| 查询缓存 | 对频繁查询结果进行缓存 | 减少数据库压力 |
| 索引优化 | 对查询字段添加索引 | 提高查询效率 |
2. 安全风险
- 数据库连接信息应加密存储
- 使用Spring Security进行权限控制
- 对敏感操作增加审计日志
- 避免使用明文密码,建议使用Vault等密钥管理工具
3. 异常处理
@ExceptionHandler
public ResponseEntity<String> handleException(Exception e) {
log.error("数据库操作异常", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("数据库操作失败");
}九、常见问题与踩坑
1. 驱动类不匹配
// 错误示例
public class MyDataSource {
public static void main(String[] args) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}问题:达梦数据库驱动类名不同
解决:确认驱动类名,达梦驱动为dm.jdbc.driver.DmDriver
2. 连接字符串错误
// 错误示例
String url = "jdbc:dm://localhost:5236/dm_db";问题:缺少参数配置
解决:添加characterEncoding=utf8参数
String url = "jdbc:dm://localhost:5236/dm_db?characterEncoding=utf8";3. 事务管理问题
// 错误示例
@Transactional
public void saveData() {
saveToMysql("test");
saveToDm("test");
}问题:跨数据源事务不支持
解决:使用分布式事务管理器(如JTA)
4. 线程安全问题
// 错误示例
public class MyService {
private String dataSource;
public void setDataSource(String dataSource) {
this.dataSource = dataSource;
}
public void save() {
DataSourceContextHolder.setDataSource(dataSource);
// ...
}
}问题:使用普通变量导致线程安全问题
解决:使用ThreadLocal保证线程隔离
十、最佳实践
1. 建议使用场景
- 多租户系统(每个租户使用独立数据库)
- 读写分离架构(主库MySQL,从库达梦)
- 两地三中心架构(主库和灾备库)
- 业务数据和日志数据分离存储
2. 不建议使用场景
- 简单业务系统(增加复杂度不值得)
- 单一业务场景(无需动态切换)
- 高并发写入场景(需考虑锁机制)
- 跨数据源的复杂业务逻辑(需分布式事务)
3. 推荐配置
spring:
datasource:
mysql:
url: jdbc:mysql://localhost:3306/mysql_db?useSSL=false&serverTimezone=UTC&connectTimeout=30000
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximumPoolSize: 100
idleTimeout: 30000
poolName: mysql-pool
dm:
url: jdbc:dm://localhost:5236/dm_db?characterEncoding=utf8&connectTimeout=30000
driver-class-name: dm.jdbc.driver.DmDriver
hikari:
maximumPoolSize: 50
idleTimeout: 30000
poolName: dm-pool十一、总结
SpringBoot配置Mysql+达梦双数据源是实现多数据库访问的核心技术。通过AbstractRoutingDataSource抽象,结合AOP和注解,可以实现灵活的数据源切换。在实际应用中需要特别注意以下几点:
- 正确配置驱动类和连接参数,特别是达梦数据库的特殊要求
- 合理设计数据源切换策略,避免不必要的切换开销
- 配置合适的连接池参数,平衡资源利用率
- 处理好跨数据源的事务问题,避免数据不一致
- 注意线程安全和异常处理,确保系统稳定性
在实际项目中,应根据业务需求选择合适的实现方案。对于需要频繁切换数据源的场景,建议使用动态路由策略;对于固定切换的场景,可以直接配置多个数据源。同时,需要关注性能优化和安全风险,确保系统在高并发和复杂业务场景下的稳定性。
评论已关闭