Magic-api 简单配置多数据源(mysql)
一、背景与问题
在现代分布式系统中,单一数据库往往难以满足业务需求。例如电商系统中,订单数据和库存数据可能需要分别存储在不同的数据库中,以实现数据隔离、性能优化或架构扩展。Magic-api 作为一款轻量级的 API 框架,提供了对多数据源的灵活支持。本文将深入探讨其多数据源配置机制,并结合真实开发场景分析其适用性与潜在问题。
二、基本原理
Magic-api 的多数据源支持基于 Spring 的 AbstractRoutingDataSource 实现,其核心原理是通过动态路由机制选择当前请求需要使用的数据库连接。具体实现分为三个关键步骤:
- 数据源注册:通过配置文件定义多个数据库连接信息
- 路由策略:通过
determineCurrentLookupKey()方法决定当前请求使用哪个数据源 - 动态切换:在数据库操作时自动切换到指定的数据源
这种设计使得应用可以在不修改业务逻辑的情况下,灵活切换数据源,适用于读写分离、分库分表等场景。
三、环境准备
# 安装依赖
mvn install# application.yml 配置文件
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/master_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/slave_db
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver四、核心实现
1. 数据源配置类
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource routingDataSource() {
AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(masterDataSource());
return routingDataSource;
}
}2. 路由策略实现
public class MyDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 从请求中获取数据源标识
String dataSourceKey = DataSourceContextHolder.getDataSource();
return dataSourceKey;
}
}3. 线程上下文管理
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}五、完整案例
1. 业务场景
构建一个电商系统,订单数据存储在主库,库存数据存储在从库:
// 订单实体类
@Entity
public class Order {
@Id
private Long id;
private String orderNo;
// 其他字段...
}
// 库存实体类
@Entity
public class Stock {
@Id
private Long id;
private String productCode;
// 其他字段...
}2. Repository 接口
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o WHERE o.orderNo = :orderNo")
Order findOrderByOrderNo(@Param("orderNo") String orderNo);
}
public interface StockRepository extends JpaRepository<Stock, Long> {
@Query("SELECT s FROM Stock s WHERE s.productCode = :productCode")
Stock findStockByProductCode(@Param("productCode") String productCode);
}3. Service 层实现
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private StockRepository stockRepository;
public Order getOrderWithStock(String orderNo, String productCode) {
// 设置数据源标识
DataSourceContextHolder.setDataSource("master");
Order order = orderRepository.findOrderByOrderNo(orderNo);
DataSourceContextHolder.setDataSource("slave");
Stock stock = stockRepository.findStockByProductCode(productCode);
return new OrderWithStock(order, stock);
}
}4. 控制器层
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/{orderNo}")
public ResponseEntity<OrderWithStock> getOrderWithStock(
@PathVariable String orderNo,
@RequestParam String productCode) {
OrderWithStock result = orderService.getOrderWithStock(orderNo, productCode);
return ResponseEntity.ok(result);
}
}六、源码解析
Magic-api 的多数据源实现核心在于 AbstractRoutingDataSource 的重写。其关键点在于:
- 动态路由机制:通过
determineCurrentLookupKey()方法动态选择数据源,该方法需要返回一个字符串标识(如"master"或"slave") - 线程安全:使用
ThreadLocal存储当前线程的数据源标识,确保每个线程独立 - 事务管理:需要配置
DataSourceTransactionManager来支持事务,注意要确保事务边界正确
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}七、进阶使用
1. 分库分表策略
public class ShardingDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String tableName = getTableNameFromRequest(); // 从请求中获取表名
return tableName;
}
}2. 读写分离实现
public class ReadWriteDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String source = getDataSourceFromRequest(); // 从请求中获取来源
return source.equals("read") ? "slave" : "master";
}
}3. 跨数据源事务管理
@Transactional
public void transferMoney(String fromAccount, String toAccount, BigDecimal amount) {
// 从主库更新账户余额
DataSourceContextHolder.setDataSource("master");
accountRepository.updateBalance(fromAccount, -amount);
// 从从库更新账户余额
DataSourceContextHolder.setDataSource("slave");
accountRepository.updateBalance(toAccount, amount);
}八、性能与工程实践
1. 性能优化
- 连接池配置:合理设置最大连接数和空闲连接数
- 索引优化:在频繁查询字段上建立索引
- 缓存机制:对高频读取的数据进行缓存
- 分页处理:避免一次性获取大量数据
# 连接池配置
spring:
datasource:
master:
hikari:
maximum-pool-size: 10
idle-timeout: 30000
connection-timeout: 300002. 安全风险
- 敏感信息泄露:配置文件中密码应加密存储
- SQL 注入:使用预编译语句防止注入攻击
- 数据一致性:跨数据源事务需要特别处理
3. 工程实践建议
- 配置文件分离:将数据源配置与业务配置分离
- 日志监控:记录数据源切换日志用于排查问题
- 压力测试:模拟高并发场景测试数据源切换性能
九、常见问题与踩坑
1. 数据源切换错误
错误示例:
public void someMethod() {
DataSourceContextHolder.setDataSource("slave");
// 未及时清除上下文
}问题:未在方法结束时清除上下文,导致后续请求使用错误数据源
解决方法:在方法结束时显式清除上下文
try {
DataSourceContextHolder.setDataSource("slave");
// 业务逻辑
} finally {
DataSourceContextHolder.clearDataSource();
}2. 事务不一致
错误示例:
@Transactional
public void transferMoney() {
// 主库更新
DataSourceContextHolder.setDataSource("master");
accountRepository.updateBalance();
// 从库更新
DataSourceContextHolder.setDataSource("slave");
accountRepository.updateBalance();
}问题:事务未正确绑定到数据源
解决方法:使用 TransactionSynchronizationManager 管理事务
3. 性能瓶颈
错误示例:频繁切换数据源导致性能下降
解决方法:使用缓存或合并查询
十、最佳实践
- 配置分离:将数据源配置与业务逻辑分离,便于维护
- 路由策略优化:根据业务需求选择合适的路由策略
- 事务管理:对跨数据源操作使用
@Transactional注解 - 监控机制:添加数据源切换日志监控
- 安全防护:使用加密存储敏感信息,防止SQL注入
十一、总结
Magic-api 的多数据源配置提供了灵活的数据库访问能力,适用于需要数据隔离、读写分离或分库分表的场景。通过合理配置和使用,可以有效提升系统性能和可维护性。但需要注意数据源切换的正确性、事务管理的完整性以及安全防护措施。在实际开发中,应根据业务需求选择合适的实现方式,避免过度设计带来的复杂性。