如何实现动态数据源?
比如,现存有两个数据源:master、salve,想实现某些方法上面使用master数据源,某些方法使用salve数据源。

数据源定义

# 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    ds:
      # 主库数据源
      master:
        url: jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root
      # 从库数据源
      slave:
        url: jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root

通过@ConfigurationProperties 来映射获得配置中的数据源信息

//省去get/set方法
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
    private String type;
    private String driverClassName;
    private Map<String,Map<String,String>> ds;
    private Integer initialSize;
    private Integer minIdle;
    private Integer maxActive;
    private Integer maxWait;

//此处的方法是为了在加载某个数据源的时候,传入公共的数据源的参数
    public DataSource buildDataSource(DruidDataSource druidDataSource){
        druidDataSource.setInitialSize(initialSize);
        druidDataSource.setMinIdle(minIdle);
        druidDataSource.setMaxWait(maxWait);
        druidDataSource.setMaxActive(maxActive);
        return druidDataSource;
    }
}

将映射完成的数据源信息加载成真正的数据源类型

@Component
@EnableConfigurationProperties(DruidProperties.class)
public class LoadDataSource {

    @Autowired
    DruidProperties druidProperties;

    /**
     * 加载配置文件中的数据源到map中
     * @return
     */
    public Map<String, DataSource> loadAllDataSource() {
        HashMap<String,DataSource> map = new HashMap<>();
        //拿到配置文件中的ds配置
        Map<String, Map<String, String>> ds = druidProperties.getDs();
        try {
            Set<String> keySet = ds.keySet();
            for (String key : keySet) {
                map.put(key, druidProperties.buildDataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(key))));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return map;
    }
}

定义数据源的注解

数据源类型控制

public interface DataSourceType {
    String DEFAULT_DATASOURCE_NAME = "master";
}

数据源注解

/**
 * 这个注解放在一个service类上或者方法上面,通过value 属性来指定类或者方法该使用哪个数据源
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DataSource {
    String value() default DataSourceType.DEFAULT_DATASOURCE_NAME;
}

数据源存储

使用ThreadLocal来存储当前的数据源名称

/**
 * 用来存放当前线程所使用的数据源的名称
 */
public class DynamicDataSourceContextHolder {

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

    public static void setDataSourceType(String dsType){
        CONTEXT_HOLDER.set(dsType);
    }
    public static String getDataSourceType(){
        return CONTEXT_HOLDER.get();
    }
    public static void clearDataSourceType(){
        CONTEXT_HOLDER.remove();
    }
}

切面解析注解


@Component
@Aspect
public class DataSourceAspect {
    /**
     * @annotation(com.origin.dynamicdatasource.annotation.DataSource) 表示方法上有@DataSurce注解的会被拦截下来
     * @within(com.origin.dynamicdatasource.annotation.DataSource) 表示如果类上有@DataSource注解的,类中的方法就会被拦截下来
     */
    @Pointcut("@annotation(com.origin.dynamicdatasource.annotation.DataSource) || @within(com.origin.dynamicdatasource.annotation.DataSource)")
    public void pc(){ }

    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp){
        //获取方法上面的有效注解
        DataSource dataSource = getDataSource(pjp);
        if(dataSource != null){
            //获取注解中的数据源名称
            String value = dataSource.value();
            DynamicDataSourceContextHolder.setDataSourceType(value);
        }
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
        return null;
    }

    /**
     * 获取注解信息
     * @param pjp
     * @return
     */
    private DataSource getDataSource(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        //查找方法上的注解
        DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if(annotation != null){
            //说明方法上有@DataSource注解
            return annotation;
        }
        //否则获取类上的@DataSource注解
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(),DataSource.class);
    }
}

设置动态数据

此环节是实现动态数据源的关键,需要重写AbstractRoutingDataSource中的determineCurrentLookupKey()方法,该方法用来返回数据源的名称,当系统需要数据源的时候,会自动调用该方法获取数据源的名称,这也是动态切换数据源的关键点,当调用该方法时,我们可以动态改变里面的数据源,然后再给系统。

@Component
public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(LoadDataSource loadDataSource) {
        //1.设置所有的数据源
        Map<String, DataSource> allDs = loadDataSource.loadAllDataSource();
        super.setTargetDataSources(new HashMap<>(allDs));
        //2.设置默认的数据源,因为并不是所有的方法上都有@DataSource注解,对于没有该注解的方法,就默认使用这里的默认数据源
        super.setDefaultTargetDataSource(allDs.get(DataSourceType.DEFAULT_DATASOURCE_NAME));
        //3.整理上面设置的两个数据源参数
        super.afterPropertiesSet();

    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

测试

Mapper

@Mapper
public interface UserMapper {

    @Select("select * from user;")
    List<User> getAllUser();
}

Service

@Service
public class UserService {

    @Autowired
    UserMapper userMapper;

    @DataSource(value = "slave")  //配置该方法使用slave数据源
    public List<User> getAllUser(){
        return userMapper.getAllUser();
    }
}

Model

public class User {
    private Integer id;
    private String username;
    private Integer age;
}

Controller

@RestController
public class DataSourceController {

    @Autowired
    UserService userService;

    @GetMapping("/getalluser")
    public List<User> getallUser(){
        return userService.getAllUser();
    }
}

数据库

image
image-1655026885630

结果

image-1655029335086

切换成master

@Service
public class UserService {

    @Autowired
    UserMapper userMapper;

    @DataSource(value = "master")//当然也可以不用设置,因为在DynamicDataSource中设置了默认的数据源master
    public List<User> getAllUser(){
        return userMapper.getAllUser();
    }
}

结果

image-1655029529683