Spring多数据源动态切换

349 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

实现原理

单数据源的情况只需要使用注解@Configuration+@Bean配合将DataSource、DataSourceTransactionManager等注入到容器中;

Spring提供了动态数据源的功能,可以让我们实现在对数据库操作前进行切换;

Spring提供了AbstractRoutingDataSource抽象类,继承重写它的determineCurrentLookupKey方法即可根据用户定义的规则选择当前的数据源。

实现逻辑:

  1. 自定义一个数据源枚举,用来指定对应数据库名称的一个String型的key值;
  2. 自定义一个DynamicDataSource类,继承AbstractRoutingDataSource抽象类,重写determineCurrentLookupKey方法(内容先不写);
  3. 创建数据源配置类,将多个数据源交由Spring容器,然后再注入一个Bean的value为dataSource的DynamicDataSource对象,然后将传入的多个数据源赋值给DynamicDataSource对象的targetDataSources属性(targetDataSources是一个Map,key即为数据源枚举的String,value则是具体的数据源),然后返回。
  4. 自定义一个数据源管理器类,使用本地线程可见变量用来存储数据源枚举String,并提供getter、setter 和remove方法;
  5. 使用aop定义切面类,@Before注解的方法根据不同包的service分别给数据源管理器类的本地线程变量赋值,@After注解的方法用来移除值。
  6. 重写determineCurrentLookupKey方法中返回数据源管理器类的get()方法返回注入ioc容器的数据源对应的key值。

具体实现

  1. 新建一个springboot项目,导入依赖,使用单数据源测试完成没有问题后再进行;
  2. 自定义一个数据源枚举,提供getter、setter ;
public enum DataSourceType {
    NEWS("news"),
    PAN("pan");
    private String type;
    DataSourceType(String type) {
        this.type = type;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
}
  1. 自定义一个DynamicDataSource类,继承AbstractRoutingDataSource抽象类
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceTypeManager.get();
    }
}
  1. 数据源的注入
@Bean(name = "newsDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.news")
    public DataSource dataSource1() {
        return DataSourceBuilder.create().build();
    }

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

    @Bean("dataSource")
    public DynamicDataSource dataSource(@Qualifier("newsDataSource") DataSource news,
                                        @Qualifier("panDataSource") DataSource pan) {
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setDefaultTargetDataSource(news);

        Map<Object, Object> targetDataSources = new HashMap<>(2);
        targetDataSources.put(DataSourceType.NEWS.getType(), news);
        targetDataSources.put(DataSourceType.PAN.getType(), pan);
        dataSource.setTargetDataSources(targetDataSources);
        return dataSource;
    }
  1. 数据源管理器类
public class DataSourceTypeManager {

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

    public static String get() {
        return DATA_SOURCE_TYPES.get();
    }
    public static void set(String dataSourceType) {
        DATA_SOURCE_TYPES.set(dataSourceType);
    }
    public static void remove() {
        DATA_SOURCE_TYPES.remove();
    }
}
  1. 接下来是切面类的代码,切入点根据service包下不同的包返回不同的枚举值,在执行对应servcie的方法时会给数据源管理器类的线程变量赋值,自定义的DynamicDataSource类则会获取这个枚举值,从IOC容器中找到具体的数据源。
@Aspect
@Component
@Order(0)
public class DataSourceAspect {

    @Before(value = "execution(* com.yang.service.news..*(..))")
    public void news() {
        DataSourceTypeManager.set(DataSourceType.NEWS.getType());
    }

    @Before(value = "execution(* com.yang.service.pan..*(..))")
    public void pan() {
        DataSourceTypeManager.set(DataSourceType.PAN.getType());
    }

    @After("execution(* com.yang.service..*(..))")
    public void after() {
        DataSourceTypeManager.remove();
    }
}