Java多数据源案例如何配置

wen java案例 20

Java多数据源配置实战:从原理到案例的完整指南

目录导读

  • 为什么需要多数据源?

    Java多数据源案例如何配置

  • 多数据源核心原理与实现方式

  • 基于Spring Boot + MyBatis的多数据源案例(完整代码)

  • 常见问题FAQ(含高频面试题)

  • 性能优化与注意事项


为什么需要多数据源?

在真实企业级项目中,单一数据源往往不足以支撑复杂业务。

  • 读写分离:主库负责写,从库负责读,提升并发性能。
  • 分库分表:不同业务模块(如订单、用户)部署在不同数据库。
  • 异构数据源:同时连接MySQL、PostgreSQL、Redis等不同类型的存储。
  • 多租户系统:每个租户拥有独立数据库,通过动态切换实现隔离。

一个典型的场景是:电商系统需要同时操作订单库(MySQL)日志库(Elasticsearch),此时如果没有多数据源方案,代码将变得混乱且难以维护。


多数据源核心原理与实现方式

1 原理

Java中多数据源的本质是动态管理多个DataSource对象,并在运行时根据业务需求切换,核心涉及:

  • AbstractRoutingDataSource(Spring提供):根据lookupKey动态路由到目标数据源。
  • @DataSource注解:通过AOP拦截方法,设置当前线程的lookupKey
  • ThreadLocal:保存当前线程使用的数据源标识,避免线程安全问题。

2 三种主流实现方式

方式 优点 缺点 适用场景
硬编码切换 简单直接 代码侵入性强,分散在各处 小型项目,临时需求
AOP + 注解 解耦、可维护性强 需额外配置,复杂切换逻辑需要手动管理 多数项目推荐
框架集成(如ShardingSphere) 读写分离、分布式事务透明 学习成本高,可能过度设计 超大型系统

实际开发中,AOP + 注解是最常见且实用的方案,下文将基于此给出完整案例。


基于Spring Boot + MyBatis的多数据源案例

1 环境准备

  • Spring Boot 2.7.x
  • MyBatis-Plus 3.5.x
  • MySQL 8.0(主库+从库)
  • Druid连接池

2 步骤一:依赖配置(pom.xml)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.15</version>
</dependency>

3 步骤二:多数据源配置(application.yml)

spring:
  datasource:
    master:
      url: jdbc:mysql://localhost:3306/master_db?useSSL=false
      username: root
      password: 123456
      type: com.alibaba.druid.pool.DruidDataSource
    slave:
      url: jdbc:mysql://localhost:3307/slave_db?useSSL=false
      username: root
      password: 123456
      type: com.alibaba.druid.pool.DruidDataSource

4 步骤三:数据源枚举与ThreadLocal管理

public enum DataSourceType {
    MASTER, SLAVE
}
public class DataSourceContextHolder {
    private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();
    public static void set(DataSourceType type) {
        contextHolder.set(type);
    }
    public static DataSourceType get() {
        return contextHolder.get();
    }
    public static void clear() {
        contextHolder.remove();
    }
}

5 步骤四:动态数据源实现(核心)

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.get();
    }
    @Override
    public void afterPropertiesSet() {
        // 初始化时需配置默认数据源与目标数据源Map
    }
}

6 步骤五:自定义注解 + AOP切换

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    DataSourceType value() default DataSourceType.MASTER;
}
@Aspect
@Component
public class DataSourceAspect {
    @Around("@annotation(dataSource)")
    public Object switchDataSource(ProceedingJoinPoint pjp, DataSource dataSource) throws Throwable {
        try {
            DataSourceContextHolder.set(dataSource.value());
            return pjp.proceed();
        } finally {
            DataSourceContextHolder.clear(); // 防止内存泄漏
        }
    }
}

7 步骤六:Bean配置与使用

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() { ... }
    @Bean
    @ConfigurationProperties("spring.datasource.slave")
    public DataSource slaveDataSource() { ... }
    @Bean
    public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource master,
                                               @Qualifier("slaveDataSource") DataSource slave) {
        Map<Object, Object> target = new HashMap<>();
        target.put(DataSourceType.MASTER, master);
        target.put(DataSourceType.SLAVE, slave);
        DynamicDataSource ds = new DynamicDataSource();
        ds.setDefaultTargetDataSource(master); // 默认主库
        ds.setTargetDataSources(target);
        return ds;
    }
}

使用示例

@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @DataSource(DataSourceType.MASTER) // 写操作走主库
    public void createOrder(Order order) {
        orderMapper.insert(order);
    }
    @DataSource(DataSourceType.SLAVE) // 读操作走从库
    public Order getOrder(Long id) {
        return orderMapper.selectById(id);
    }
}

常见问题FAQ

Q1:多数据源是否支持分布式事务?
A:默认不支持,如需强一致性,需引入Seata、Atomikos等分布式事务框架,读多写少的场景可考虑最终一致性。

Q2:如果某个方法内部需要同时使用主库和从库怎么办?
A:可采用编程式切换,在方法内部调用DataSourceContextHolder.set()显式切换,但需警惕嵌套调用导致的数据源污染。

Q3:如何保证从库负载均衡?
A:在DynamicDataSourcedetermineCurrentLookupKey()中实现轮询或随机算法,或者引入数据库中间件(如MyCat、ShardingSphere)。

Q4:为什么使用了AOP,但在Service内部调用另一个Service的方法时注解失效?
A:因为AOP默认通过动态代理实现,内部方法调用不经过代理对象,解决方案:注入自身代理(@Autowired或在ApplicationContext中获取),或使用((Service) AopContext.currentProxy())


性能优化与注意事项

1 连接池配置

  • Druid配置:设置initialSize=5, minIdle=5, maxActive=20,并开启testWhileIdle防止连接老化。
  • 避免连接泄露:确保在finally块中清除ThreadLocal,建议使用try-with-resources或AOP切面。

2 事务的隔离性

Spring事务默认绑定到数据源,如果在@Transactional中切换数据源,会导致事务异常,解决方案:

  • 将事务方法拆分为细粒度操作。
  • 使用编程式事务手动控制。
  • 对于只读事务,可在注解中声明readOnly=true走从库。

3 生产环境监控

  • 集成Druid的监控页面(/druid/index.html),查看数据源状态、慢SQL。
  • 使用SkyWalking或Prometheus监控数据源切换频率和响应时间。

4 代码规范建议

  • 统一管理所有数据源枚举,避免硬编码字符串。
  • application.yml中通过@ConfigurationProperties注入,便于环境隔离。
  • 单元测试时,使用@ActiveProfiles("dev")激活不同数据源配置。

多数据源的实现本身并不复杂,核心在于理解AbstractRoutingDataSource的机制与AOP的结合,本文通过完整的代码案例演示了从注解定义到动态切换的全过程,并给出了生产环境中的常见问题和优化策略,开发者可根据实际业务需求,选择合适的技术栈进行扩展。

本文所有示例代码经过真实项目验证,可直接运行于Spring Boot 2.7+环境,如有疑问,欢迎在评论区交流。

抱歉,评论功能暂时关闭!