若依Druid多数据源配置及使用代码详解

关键思路:

        通过Druid提供的可运行时动态选择数据源的AbstractRoutingDataSource入手,只要我们的数据源类采用该类型便也可实现动态选择数据源了,由于该类是抽象类,因此我们的数据源类只要继承该类即可。

        AbstractRoutingDataSource根据determineCurrentLookupKey方法的返回值进行数据源的选取,因此问题转变为如何实现运行时动态修改determineCurrentLookupKey的返回值即可,我们知道线程是程序执行的基本单位,我们的数据库操作也是在一个线程中完成的,因此若我们需要动态选择数据源,则只需要让determineCurrentLookupKey的返回值随着线程改变即可,因此很容易可以想到使用ThreadLocal变量来实现。

代码讲解:

1)数据源Key的动态切换是必须的,若依中使用以下类对ThreadLocal变量进行了封装

public class DynamicDataSourceContextHolder
{
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

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

    /**
     * 设置数据源的变量
     */
    public static void setDataSourceType(String dsType)
    {
        log.info("切换到{}数据源", dsType);
        CONTEXT_HOLDER.set(dsType);
    }

    /**
     * 获得数据源的变量
     */
    public static String getDataSourceType()
    {
        return CONTEXT_HOLDER.get();
    }

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

2)有了ThreadLocal变量后,接下来就是继承AbstractRoutingDataSource类并覆盖其determineCurrentLookupKey方法,使其返回值跟上面的ThreadLocal变量绑定。

public class DynamicDataSource extends AbstractRoutingDataSource
{

    /**
     * 配置默认数据源
     * @param defaultTargetDataSource 默认数据源
     * @param targetDataSources 数据源map,所有数据源保存至此
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources)
    {
// 设置默认数据源,在key为null时使用默认数据源
        super.setDefaultTargetDataSource(defaultTargetDataSource);
// 设置数据源map,以供通过key动态获取所要使用的数据源
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    /**
     * 获取数据源的key,通过key到上面的数据源map中获取数据源,若获取到则改变数据源,若找不到指定key的数据源则使用默认数据源。
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey()
    {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

3)上面的代码已经实现了能够动态切换的自定义数据源了,接下来就是让druid使用该数据源实例进行自动装配数据源。

@Configuration
public class DruidConfig
{
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
// 根据配置决定是否使用配置文件相应配置进行bean的注入
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean(name = "dynamicDataSource")
// 使该数据源bean被优先用于数据源的自动装配
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        // 初始化数据源容器,装入主数据源
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        // 装入slave数据源,也可以使用上行代码的方式,不过需要捕获异常。
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
    
    /**
     * 设置数据源
     * 
     * @param targetDataSources 备选数据源集合
     * @param sourceName 数据源名称
     * @param beanName bean名称
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        /* 避免在无slave配置时抛出异常*/
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
        }
    }
}
  • 上面的masterDataSource和slaveDataSource方法没什么特别的,主要就是根据配置文件对DruidDataSource进行实例化并注入bean罢了,DruidProperties只是对DruidDataSource进行了一层封装,其目的是复用通用数据源配置,如果你的每个数据源没有相同的配置,那么这种写法就不适合。
  • dataSource方法是最为重要的!!!在这里会将masterDataSource数据源作为参数传入该方法中,这里参数名必须跟上面方法名保持一致(因为上面@Bean没有制定bean名称,因此master数据源的bean为方法名)。里面就是实例化DynamicDataSource并注入bean,没啥特别的。这里@Primary非常重要,它会替代默认的数据源进行数据源的自动装配,因此不加这个会导致自定义数据源无法使用。
  • DynamicDataSource之所以使用@Primary注解后可以替代默认数据源是因为DynamicDataSource继承了AbstractRoutingDataSource类,查看源码可以发现它间接实现了DataSource接口,因此其为有效数据源,在系统自动装配数据源时DynamicDataSource满足要求,且被@Primary修饰,因此会优先被选用。

4)至此,动态数据源就都实现了,之后的便是根据需要在指定操作前修改DynamicDataSourceContextHolder中的CONTEXT_HOLDER的值就行了。为了避免过度侵入代码以及复用修改CONTEXT_HOLDER的代码,因此推荐使用AOP进行实现。

@Aspect
@Order(1)
@Component
public class DataSourceAspect
{
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.ruoyi.common.annotation.DataSource)"
            + "|| @within(com.ruoyi.common.annotation.DataSource)")
    public void dsPointCut()
    {

    }

    @Around("dsPointCut()")
    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();
        }
    }

    /**
     * 获取需要切换的数据源
     */
    public DataSource getDataSource(ProceedingJoinPoint point)
    {
        MethodSignature signature = (MethodSignature) point.getSignature();
        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if (Objects.nonNull(dataSource))
        {
            return dataSource;
        }

        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    }
}

5)使用时只需要在类或代码上进行添加@DataSource注解即可,注解代码如下。

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited  // 表明该注解可被标注的类的子类继承
public @interface DataSource
{
    /**
     * 切换数据源名称
     */
    public DataSourceType value() default DataSourceType.MASTER;
}

结语:

        正好最近又开始熟悉自己的项目了,就写篇文章沉淀沉淀,如果大家觉得对你有帮助,还望不吝啬你的点赞和收藏💐

        此外,笔者水平有限,如果本文讲解有哪里不对,还望评论指正,我们一起努力,共同进步!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值