应用场景

系统用户只能访问系统配置的数据源(可动态新增修改的)

RuoYi Vue 框架源码

点我跳转

实现方式

1.系统提供

Yml文件配置 + Druid加载数据源 + @DataSource注解 + DataSourceAspect增强

@DataSource 可以应用在类或方法上,如果同时存在,优先于方法上声明的数据源

// DataSourceType.MASTER 是一个枚举类里声明的主数据源 
@DataSource(DataSourceType.MASTER)
public Map<String, Object> queryAll() {
    // body
}

DataSourceAspect 实现功能主要代码:

// 切换数据源 dataSource.value().name() 就是@DataSource注解所使用的数据源名称
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());

// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();

2.自定义实现

Yml文件配置 + Druid加载数据源(下面有新增数据源方法) + @CustomDataSource注解 + CustomDataSourceAspect增强

Yml文件配置与上一实现方式中无差别,配置一个或多个正常使用的数据源。
Druid加载会在 SpringBootApplication 启动装配Bean时自动注入。

/**
 * 新增数据源
 * @param newDataSource 新数据源配置类
 * @return boolean 是否添加成功
 */
public static boolean addDataSource(DruidDataSource newDataSource) {
    Map<Object, Object> targetDataSources = new HashMap<>();
    boolean result = true;
    try {
        // 获取 yml 文件里已经注入进去的bean
        targetDataSources.put(DataSourceType.MASTER.name(), SpringUtils.getBean("masterDataSource"));
        targetDataSources.put("otherDataSource", SpringUtils.getBean("otherDataSource"));
        
        // DataSource 数据源相关配置
        newDataSource.setDbType("com.alibaba.druid.pool.DruidDataSource");
        newDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        newDataSource.setName("newDataSource");
        newDataSource.setUrl("数据源URL");
        newDataSource.setUsername("用户名");
        newDataSource.setPassword("密码");
        targetDataSources.put("newDataSource", newDataSource);
        
        DynamicDataSource dynamicDataSource = SpringUtils.getBean("dynamicDataSource");
        dynamicDataSource.setTargetDataSources(targetDataSources);
        
        // 重新加载数据源
        dynamicDataSource.afterPropertiesSet();
    } catch (Exception e) {
        // 处理异常 ...
        logger.error("新增数据源错误")
        result = false;
    }
    
    return result;
}

定义 @CustomDataSource 注解

import java.lang.annotation.*;

/**
 * 自定义多数据源切换注解可声明在类上或声明在方法上
 * 如果定义在类上则本类全部方法都会进行数据源切换处理
 * 如果定义在方法上则数据源切换只应用于方法上
 *
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CustomDataSource { }

定义 CustomDataSourceAspect 增强

import com.ruoyi.common.annotation.TestDataSource;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.datasource.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Objects;

/**
 * 多数据源处理
 * 
 */
@Aspect
@Order(1)
@Component
public class CustomDataSourceAspect {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Pointcut("@annotation(com.ruoyi.common.annotation.CustomDataSource)"
            + "|| @within(com.ruoyi.common.annotation.CustomDataSource)")
    public void customPointCut() { }

    @Around("customPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        CustomDataSource dataSource = getDataSource(point);
        if (StringUtils.isNotNull(dataSource)) {
            // 根据当前用户查询数据源并切换数据源
            LoginUser loginUser = SecurityUtils.getLoginUser();
            String dataSourceType = "实际配置的数据源名称";
            // 切换数据源
            DynamicDataSourceContextHolder.setDataSourceType(dataSourceType);
        }

        try {
            return point.proceed();
        } finally {
            // 销毁数据源 在执行方法之后
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

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

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

tips:

// 修改默认数据源方式
dynamicDataSource.setDefaultTargetDataSource(数据源配置类);

至此系统用户登录后发送请求,如果存在 CustomDataSourc 注解则会根据查询被分配的数据源,否则查询主数据源(master)的数据