springboot整合mybatis配置多数据源(mysql/oracle)

'# Spring Boot整合MyBatis配置多数据源(MySQL/Oracle)

一、背景与问题

在分布式系统中,多数据源架构是常见需求。例如电商系统可能需要:

  • 订单数据存储在MySQL
  • 会员信息存储在Oracle
  • 日志系统使用独立数据库

传统单数据源架构无法满足这种需求,需要通过多数据源配置实现:

  1. 数据隔离:不同业务模块使用独立数据库
  2. 数据分片:水平分库分表
  3. 混合架构:同时对接关系型和非关系型数据库

但多数据源架构也带来复杂性:

  • 数据源切换逻辑
  • 事务一致性问题
  • 性能损耗
  • 系统复杂度提升

二、基本原理

Spring Boot整合MyBatis的多数据源配置,本质是通过AbstractRoutingDataSource实现动态数据源切换。其核心机制如下:

  1. 数据源抽象层:通过AbstractRoutingDataSource抽象多数据源
  2. 路由策略:实现determineCurrentLookupKey()方法决定使用哪个数据源
  3. 数据源注册:通过DataSourceProperties注册多个数据源
  4. 事务管理:使用DataSourceTransactionManager管理事务

三、环境准备

# application.yml
spring:
  datasource:
    mysql:
      url: jdbc:mysql://localhost:3306/mysql_db
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    oracle:
      url: jdbc:oracle:thin:@localhost:1521:orcl
      username: sys
      password: oracle
      driver-class-name: oracle.jdbc.OracleDriver

四、核心实现

1. 动态数据源配置类

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.mysql")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.oracle")
    public DataSource oracleDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource routingDataSource() {
        // 创建抽象数据源
        AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource();
        
        // 设置目标数据源
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("mysql", mysqlDataSource());
        targetDataSources.put("oracle", oracleDataSource());
        
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(mysqlDataSource());
        return routingDataSource;
    }
}

关键代码解释:

  • 使用DataSourceBuilder创建数据源
  • 通过AbstractRoutingDataSource实现多数据源
  • setTargetDataSources注册多个数据源
  • setDefaultTargetDataSource设置默认数据源

2. 数据源路由策略

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();
    }
}
public class DynamicDataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

关键点:

  • 使用ThreadLocal实现线程隔离
  • 通过determineCurrentLookupKey方法决定数据源
  • 需要继承AbstractRoutingDataSource实现路由逻辑

3. 事务管理配置

@Configuration
public class TransactionConfig {

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

五、完整案例

1. 电商系统案例

场景:订单系统使用MySQL,库存系统使用Oracle

1.1 数据库配置

spring:
  datasource:
    mysql:
      url: jdbc:mysql://localhost:3306/order_db
      username: root
      password: root
    oracle:
      url: jdbc:oracle:thin:@localhost:1521:inventory_db
      username: sys
      password: oracle

1.2 数据源路由配置

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.mysql")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.oracle")
    public DataSource oracleDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource routingDataSource() {
        AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("mysql", mysqlDataSource());
        targetDataSources.put("oracle", oracleDataSource());
        routingDataSource.setTargetDataSources(targetDataSources);
        routingDataSource.setDefaultTargetDataSource(mysqlDataSource());
        return routingDataSource;
    }
}

1.3 业务逻辑

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public void createOrder(Order order) {
        DataSourceContextHolder.setDataSource("mysql");
        orderMapper.insert(order);
    }
}
@Service
public class InventoryService {

    @Autowired
    private InventoryMapper inventoryMapper;

    public void updateInventory(Inventory inventory) {
        DataSourceContextHolder.setDataSource("oracle");
        inventoryMapper.update(inventory);
    }
}

六、源码解析

1. AbstractRoutingDataSource原理

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    protected Map<Object, Object> targetDataSources = new LinkedHashMap<>();
    protected Object defaultTargetDataSource;

    protected abstract Object determineCurrentLookupKey();
}

关键机制:

  • 通过determineCurrentLookupKey()获取当前数据源标识
  • 使用ThreadLocal实现线程隔离
  • 支持动态切换数据源

2. 数据源切换过程

  1. 调用setDataSource("mysql")设置当前数据源
  2. 在SQL执行时,AbstractRoutingDataSource会根据当前线程的ThreadLocal获取数据源
  3. 实际执行时会从targetDataSources中获取对应的数据源

七、进阶使用

1. 动态数据源切换策略

public class CustomDataSourceRouter extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = getDataSourceFromRequest();
        return dataSource != null ? dataSource : "mysql";
    }
}

2. 多数据源事务管理

@Transactional
public void transferMoney(String from, String to, BigDecimal amount) {
    DataSourceContextHolder.setDataSource("mysql");
    accountService.transfer(from, to, amount);
    
    DataSourceContextHolder.setDataSource("oracle");
    inventoryService.updateInventory();
}

八、性能与工程实践

1. 性能优化策略

  1. 连接池配置:使用HikariCP优化连接池

    spring:
      datasource:
        mysql:
          hikari:
            maximum-pool-size: 10
            idle-timeout: 30000
            connection-timeout: 30000
  2. SQL优化:避免全表扫描,使用索引
  3. 缓存机制:对频繁访问的数据使用Redis缓存

2. 安全风险分析

  1. 数据库权限:避免使用高权限账户
  2. SQL注入:使用预编译语句
  3. 数据隔离:确保不同数据源的隔离性

九、常见问题与踩坑

1. 常见错误

错误示例:

public class MyService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void test() {
        jdbcTemplate.execute("SELECT * FROM orders");
    }
}

问题分析:

  • 未指定数据源
  • 默认使用主数据源
  • 可能导致数据错乱

解决方法:

public void test() {
    DataSourceContextHolder.setDataSource("mysql");
    jdbcTemplate.execute("SELECT * FROM orders");
}

2. 事务管理问题

错误场景:

@Transactional
public void transfer() {
    DataSourceContextHolder.setDataSource("mysql");
    accountService.transfer();
    
    DataSourceContextHolder.setDataSource("oracle");
    inventoryService.update();
}

问题分析:

  • 事务未跨越数据源
  • 可能导致部分操作失败

解决方法:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transfer() {
    accountService.transfer();
    inventoryService.update();
}

十、最佳实践

1. 推荐方案

  1. 数据源隔离:每个业务模块使用独立数据源
  2. 路由策略:根据业务逻辑动态切换数据源
  3. 事务管理:使用@Transactional保证事务一致性
  4. 连接池优化:配置合理的连接池参数
  5. 安全控制:限制数据库访问权限

2. 避免使用场景

  1. 数据源数量过多:超过3个数据源时应考虑其他方案
  2. 复杂事务场景:涉及多个数据源的事务需要特殊处理
  3. 频繁切换:避免不必要的数据源切换

十一、总结

Spring Boot整合MyBatis配置多数据源是构建复杂系统的重要技术。通过AbstractRoutingDataSource实现动态数据源切换,结合事务管理和连接池优化,可以有效应对多数据源场景下的业务需求。实际应用中应根据业务复杂度选择合适的实现方案,注意避免常见错误,合理进行性能优化和安全控制。多数据源架构虽然复杂,但合理的设计可以带来显著的系统扩展性和灵活性。

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日