SpringBoot @DS注解 和 DynamicDataSource自定义实现多数据源的2种实现方式

前言

在实际的项目中,我们经常会遇到需要操作多个数据源的情况,SpringBoot为我们提供了多种实现多数据源的方式。本文将介绍两种常见的方式:使用@DS注解实现多数据源的切换以及使用DynamicDataSource自定义实现多数据源的切换。

我们将分别介绍这两种方法的实现原理和代码实现,并对比它们的优劣势。

方式一、使用DynamicDataSource实现多数据源

1、在application.yml文件中配置多个数据源

# 数据源配置
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        druid:
            # 主库数据源
            master:
                url: jdbc:mysql://xxx.xxx.xxx.101:3306/test1?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
                username: root
                password: root123
            # 从库数据源
            slave:
              # 从数据源开关/默认关闭
                enabled: true
                url: jdbc:mysql://xxx.xxx.xxx.102:3306/test2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
                username: root123
                password: root123
            oracle:
              enabled: true
              url: jdbc:oracle:thin:@//xxx.xxx.xxx.103:1521/test3?useUnicode=true&characterEncoding=AL32UTF8
              username: root
              password: root123

2、添加数据源到targetDataSources集合中

创建配置文件,把多个数据源添加到targetDataSources集合中,然后返回动态数据源配置。

/**
 * druid 配置多数据源
 * 
 * @author admin
 */
@Configuration
public class DruidConfig
{

    /**
     * master数据源的配置
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties)
    {
        // 创建Druid数据源
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        // 返回由Druid属性配置后的数据源
        return druidProperties.dataSource(dataSource);
    }

    /**
     * slave数据源的配置
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties)
    {
        // 创建Druid数据源
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        // 返回由Druid属性配置后的数据源
        return druidProperties.dataSource(dataSource);
    }

    /**
     * oracle数据源的配置
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.oracle")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.oracle", name = "enabled", havingValue = "true")
    public DataSource oracleDataSource(DruidProperties druidProperties)
    {
        // 创建Druid数据源
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        // 设置Oracle数据库驱动类
        dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
        // 返回由Druid属性配置后的数据源
        return druidProperties.dataSource(dataSource);
    }

    /**
     * 动态数据源配置
     */
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        // 目标数据源的映射
        Map<Object, Object> targetDataSources = new HashMap<>();
        // 将master数据源放入映射中
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        // 将slave数据源放入映射中
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
        // 将oracle数据源放入映射中
        setDataSource(targetDataSources, DataSourceType.ORACLE.name(), "oracleDataSource");
        // 返回动态数据源对象
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }


    /**
     * 设置数据源
     * 
     * @param targetDataSources 备选数据源集合
     * @param sourceName 数据源名称
     * @param beanName bean名称
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
        }
    }
}

上述代码中DynamicDataSource继承了AbstractRoutingDataSource抽象类,AbstractRoutingDataSource抽象类是动态数据源的核心实现类,它包含了几个关键方法:

  • determineCurrentLookupKey():这是一个抽象方法,必须由具体的子类实现。它的作用是决定当前应该使用哪个数据源的标识。当应用程序需要访问数据库时,AbstractRoutingDataSource会调用这个方法来确定要使用的数据源。
  • setTargetDataSources(Map<Object, Object> targetDataSources):这个方法用于设置目标数据源的Map。Map中的键值对表示数据源的标识和对应的数据源实例。当AbstractRoutingDataSource需要根据标识选择数据源时,会根据这个Map来进行查找。
  • setDefaultTargetDataSource(Object defaultTargetDataSource):这个方法用于设置默认的数据源。当在无法确定要使用的数据源时,AbstractRoutingDataSource会使用默认的数据源。
  • afterPropertiesSet():这个方法用于在设置完属性后进行一些必要的初始化工作。例如,在设置完目标数据源和默认数据源后,需要调用这个方法来确保AbstractRoutingDataSource的正确初始化。

通过show dragrams我们可以看到AbstractRoutingDataSource抽象类的关系图:

再看DynamicDataSource的具体实现:

public class DynamicDataSource extends AbstractRoutingDataSource
{
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
        // 调用父类构造函数设置默认数据源和目标数据源
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet(); // 初始化
    }

    @Override
    protected Object determineCurrentLookupKey()
    {
        // 获取当前数据源的标识符
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

它主要做的事情就是调用父类构造函数设置默认数据源和目标数据源,完成初始化,然后重写determineCurrentLookupKey() 方法,返回当前数据源的标识。

3、动态数据源的使用

在实际开发过程中,我们通常使用开发注解的方式完成多数据源的切换,现在我们创建一个注解@DataSource,使用注解:

    @DataSource(value = DataSourceType.ORACLE)
    public Object test()
    {
        return userMapper.selectUserList();
    }

注解核心实现:

    @Around("dataPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable
    {
        DataSource dataSource = getDataSource(point);
        // 获取数据源的名称
        if (StringUtils.isNotNull(dataSource)) {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
        }
        try {
            return point.proceed();
        }
        finally {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

DynamicDataSourceContextHolder类实现,DynamicDataSourceContextHolder类使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本。然后根据set赋值获取自己当前所需的数据源,而不会影响其它线程所对应的副本。

具体实现如下:

public class DynamicDataSourceContextHolder {
    
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    
    /**
     *  使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> THREAD_LOCAL_DATA = new ThreadLocal<>();

    /**
     * 设置数据源
     */
    public static void setDataSourceType(String dsType) {
        THREAD_LOCAL_DATA.set(dsType);
    }

    /**
     * 获得数据源
     */
    public static String getDataSourceType() {
        return THREAD_LOCAL_DATA.get();
    }

    /**
     * 清空数据源
     */
    public static void clearDataSourceType()
    {
        THREAD_LOCAL_DATA.remove();
    }
}

4、动态数据源实现流程总结:

首先,在Spring Boot应用的配置文件中配置多个数据源,例如MySQL和Oracle数据源,并创建一个自定义的DynamicDataSource类,继承AbstractRoutingDataSource。

在DynamicDataSource类中,需要重写determineCurrentLookupKey方法,该方法根据当前线程上下文中保存的数据源类型来确定当前应该使用的数据源。

接着,可以通过AOP拦截器或其他方式,在每次数据库操作之前根据业务逻辑设置当前线程的数据源类型到ThreadLocal中。这样在调用数据库操作时,DynamicDataSource会根据ThreadLocal中保存的数据源类型来选择对应的数据源进行操作。

最后,通过自定义注解@DataSource(value = DataSourceType.ORACLE)来标记需要切换数据源的方法或类,利用AOP切面编程,在方法执行前根据注解值设置当前线程的数据源类型,从而实现动态数据源切换的功能。

方式二、使用@DS注解实现多数据源

1、添加pom依赖:

注解@DS是基于dynamic-datasource-spring-boot-starter 实现多数据源切换的,添加pom依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

2、配置数据源:

在application.properties或application.yml文件中配置多个数据源的连接信息。

spring:
  datasource:
    dynamic:
      primary:  # 主数据源
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/db1
        username: user
        password: password
      secondary:  # 第二个数据源
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/db2
        username: user
        password: password

3、使用数据源:

在服务类或数据访问层中通过@DS注解来指定使用哪个数据源,例如:

@DS("primary")
public List<User> listUsersFromPrimaryDataSource() {
    return userDao.listUsers();
}

@DS("secondary")
public List<User> listUsersFromSecondaryDataSource() {
    return userDao.listUsers();
}

4、实现原理:

从DynamicDataSourceAutoConfiguration作为入口,我们可以看@DS注解的实现原理

其实通过源码跟踪,不难发现@DS实现数据源切换,也是根据 AOP 切面、注解、ThreadLocal 等方式实现。@DS注解的封装,可以在方法或类上直接指定数据源,而无需修改代码。这使得切换数据源变得更加简单和直接。

通过源码查看:

其实,通过上述源码查看,可大致概括@DS注解的实现原理,主要如下:

扫描数据源配置信息: 在启动时,DynamicDataSourceAutoConfiguration 会扫描项目中定义的数据源配置信息,例如在配置文件中定义的多个数据源的连接信息。

创建数据源对象: 根据扫描到的数据源配置信息,动态创建对应的数据源对象,可以是基于不同数据库的 DataSource 实现类,如 DruidDataSource、HikariDataSource 等。

数据源路由策略: 定义数据源的路由策略,即根据业务需求或者特定条件来决定使用哪个数据源。这可以通过 AOP 切面、注解、ThreadLocal 等方式实现。

数据源切换: 在需要访问数据库的地方,根据路由策略选择合适的数据源,并将该数据源设置为当前线程的数据源上下文中,以确保后续的数据库操作都使用选定的数据源。

数据源清理: 在数据源使用完毕后,需要及时清理数据源上下文,避免数据源泄漏或混乱。

总的来说,DynamicDataSourceAutoConfiguration 的实现原理主要涉及数据源的创建、路由策略的制定和数据源的切换管理。通过这些步骤,可以实现在运行时动态切换数据源,从而实现多数据源的灵活应用。

以上就是SpringBoot @DS注解 和 DynamicDataSource自定义实现多数据源的实现方式,可根据实际业务需要进行选择和调整。

  • 31
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值