自学内容网 自学内容网

动态数据源多种实现方式及对比详细介绍

动态数据源实现方式


1. 概述

动态数据源是现代企业级应用中常见的需求,尤其是在 多数据源管理读写分离多租户系统分库分表 等场景中,动态切换不同的数据源可以提升系统的灵活性和性能。本文将详细介绍几种常见的动态数据源实现方式,包括其应用场景、实现步骤、优缺点对比。


2. 动态数据源实现方式
2.1 基于 AbstractRoutingDataSource 实现动态数据源

适用场景
主要适用于需要根据业务上下文动态切换数据源的场景,常用于 读写分离多租户系统 等应用。

实现原理
Spring 框架提供的 AbstractRoutingDataSource 类支持动态数据源路由。通过继承该类并重写 determineCurrentLookupKey() 方法,基于当前线程上下文中的数据源标识符进行数据源切换。

实现步骤

  1. 创建数据源配置类:定义多个数据源,并将它们注入 DynamicDataSource
@Configuration
public class DataSourceConfig {

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

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

    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource(@Qualifier("dataSource1") DataSource dataSource1,
                                        @Qualifier("dataSource2") DataSource dataSource2) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("db1", dataSource1);
        targetDataSources.put("db2", dataSource2);

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(dataSource1);
        return dynamicDataSource;
    }
}
  1. 创建 DynamicDataSourceContextHolder:使用 ThreadLocal 管理数据源标识。
public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    public static void setDataSourceType(String dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
}
  1. 创建 DynamicDataSource:继承 AbstractRoutingDataSource 并重写 determineCurrentLookupKey() 方法。
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}
  1. 使用 AOP 切面动态切换数据源
@Aspect
@Component
public class DynamicDataSourceAspect {

    @Before("@annotation(ds)")
    public void switchDataSource(JoinPoint point, DataSource ds) {
        DynamicDataSourceContextHolder.setDataSourceType(ds.value());
    }

    @After("@annotation(ds)")
    public void clearDataSource(JoinPoint point, DataSource ds) {
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}
  1. 使用自定义注解 @DataSource 指定数据源:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "db1";
}

优点

  • 实现简单,轻松与 Spring 框架集成。
  • 适用于小型项目的动态数据源需求。

缺点

  • 性能可能在复杂场景中遇到瓶颈。

2.2 基于 Spring AOP 实现动态数据源

适用场景
适合需要细粒度动态切换数据源的场景,比如特定方法或业务逻辑需要在不同的数据源中执行。

实现原理
通过 Spring AOP 技术,在方法执行前后拦截调用,根据自定义注解切换数据源。

实现步骤

  1. 创建自定义注解 @DataSource
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value();
}
  1. 定义 AOP 切面类
@Aspect
@Component
public class DynamicDataSourceAspect {

    @Before("@annotation(ds)")
    public void switchDataSource(JoinPoint point, DataSource ds) {
        DynamicDataSourceContextHolder.setDataSourceType(ds.value());
    }

    @After("@annotation(ds)")
    public void clearDataSource(JoinPoint point, DataSource ds) {
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}
  1. 在业务层使用 @DataSource 注解 切换数据源:
@Service
public class UserService {

    @DataSource("db1")
    public List<User> getAllUsersFromDb1() {
        // 从 db1 获取数据
    }

    @DataSource("db2")
    public List<User> getAllUsersFromDb2() {
        // 从 db2 获取数据
    }
}

优点

  • 实现灵活,支持细粒度的控制。
  • 易于使用,适合小范围数据源切换。

缺点

  • 随着项目的规模增加,过多的切面可能导致维护复杂性上升。

2.3 基于 TransactionManager 实现动态数据源

适用场景
用于需要在不同数据源之间切换并保持事务一致性的场景,特别适合有 多数据源事务管理 需求的应用。

实现原理
通过为每个数据源配置不同的 TransactionManager,在 @Transactional 注解中动态指定事务管理器,确保数据源切换时的事务一致性。

实现步骤

  1. 配置每个数据源的 TransactionManager
@Bean
public PlatformTransactionManager transactionManager1(@Qualifier("dataSource1") DataSource dataSource1) {
    return new DataSourceTransactionManager(dataSource1);
}

@Bean
public PlatformTransactionManager transactionManager2(@Qualifier("dataSource2") DataSource dataSource2) {
    return new DataSourceTransactionManager(dataSource2);
}
  1. 使用 @Transactional 注解指定事务管理器
@Service
public class UserService {

    @Transactional("transactionManager1")
    public void performDb1Operation() {
        // 操作 db1
    }

    @Transactional("transactionManager2")
    public void performDb2Operation() {
        // 操作 db2
    }
}

优点

  • 支持多数据源的事务管理,保证数据一致性。
  • 适合需要事务支持的场景。

缺点

  • 配置复杂,特别是涉及到分布式事务时。

2.4 通过数据库中间件实现动态数据源(ShardingSphere、MyCAT)

适用场景
适用于 大规模分库分表读写分离 等复杂场景,尤其是对性能有较高要求的项目。

实现原理
使用数据库中间件(如 ShardingSphere、MyCAT),通过中间件实现数据源路由、分片、读写分离等功能。开发者不需要手动处理数据源切换,所有逻辑由中间件根据配置文件自动完成。

实现步骤

  1. 引入 ShardingSphere 依赖
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-jdbc-core</artifactId>
    <version>x.x.x</version>
</dependency>
  1. 配置数据源和路由规则

application.yml 中配置数据源和分片规则。

sharding:
  data-sources:
    ds_0:
      url: jdbc:mysql://localhost:3306/db0
      username: root
      password: root
    ds_1:
      url: jdbc:mysql://localhost:3306/db1
      username: root
      password: root

优点

  • 中间件提供高性能支持,适合复杂的大规模场景。
  • 减少了开发者手动管理多数据源的工作量。

缺点

  • 学习成本较高,配置复杂。
  • 对中间件的稳定性有较高的依赖。


3. 各实现方式对比
实现方式适用场景优点缺点
AbstractRoutingDataSource读写分离、多租户简单易用,与 Spring 框架无缝集成在复杂场景中可能存在性能瓶颈
基于 Spring AOP细粒度数据源切换灵活,支持特定方法和类的动态切换过多切面可能增加调试和维护难度
基于 TransactionManager多数据源事务管理保证多数据源场景中的事务一致性配置复杂,特别是分布式事务时
数据库中间件(ShardingSphere等)分库分表、读写分离性能优异,适合复杂场景,开发者不需手动处理学习成本高,依赖中间件性能和稳定性
手动切换数据源特殊业务场景完全控制数据源切换,灵活实现复杂,维护成本高

4. 总结

动态数据源技术为企业级应用提供了灵活的数据库管理能力。不同的实现方式各有优劣,开发者需要根据项目规模、业务需求和性能要求进行选择。

  • 小型项目:可以选择基于 AbstractRoutingDataSource 或 Spring AOP 的实现,简单易用,能够快速满足动态切换需求。
  • 需要事务管理:对于涉及多数据源事务管理的项目,基于 TransactionManager 的方式更为适合,可以保证数据一致性。
  • 大规模项目:如果是分库分表或读写分离场景,数据库中间件(如 ShardingSphere)能够提供高性能支持。
  • 特殊需求:对于少数特殊业务场景,手动切换数据源可能是最灵活的方案,但维护成本较高。

通过合理的动态数据源选择与实现,能够显著提升系统的扩展性和性能,满足不同复杂业务场景的需求。


原文地址:https://blog.csdn.net/qq_42631788/article/details/142420958

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!