持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
实现原理
单数据源的情况只需要使用注解@Configuration+@Bean配合将DataSource、DataSourceTransactionManager等注入到容器中;
Spring提供了动态数据源的功能,可以让我们实现在对数据库操作前进行切换;
Spring提供了AbstractRoutingDataSource抽象类,继承重写它的determineCurrentLookupKey方法即可根据用户定义的规则选择当前的数据源。
实现逻辑:
- 自定义一个数据源枚举,用来指定对应数据库名称的一个String型的key值;
- 自定义一个DynamicDataSource类,继承AbstractRoutingDataSource抽象类,重写determineCurrentLookupKey方法(内容先不写);
- 创建数据源配置类,将多个数据源交由Spring容器,然后再注入一个Bean的value为dataSource的DynamicDataSource对象,然后将传入的多个数据源赋值给DynamicDataSource对象的targetDataSources属性(targetDataSources是一个Map,key即为数据源枚举的String,value则是具体的数据源),然后返回。
- 自定义一个数据源管理器类,使用本地线程可见变量用来存储数据源枚举String,并提供getter、setter 和remove方法;
- 使用aop定义切面类,@Before注解的方法根据不同包的service分别给数据源管理器类的本地线程变量赋值,@After注解的方法用来移除值。
- 重写determineCurrentLookupKey方法中返回数据源管理器类的get()方法返回注入ioc容器的数据源对应的key值。
具体实现
- 新建一个springboot项目,导入依赖,使用单数据源测试完成没有问题后再进行;
- 自定义一个数据源枚举,提供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;
}
}
- 自定义一个DynamicDataSource类,继承AbstractRoutingDataSource抽象类
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceTypeManager.get();
}
}
- 数据源的注入
@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;
}
- 数据源管理器类
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();
}
}
- 接下来是切面类的代码,切入点根据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();
}
}