在最近的開發(fā)中需要在業(yè)務(wù)數(shù)據(jù)庫(kù)之外訪問(wèn)大數(shù)據(jù)提供的數(shù)據(jù)绷柒,所以使用到了多數(shù)據(jù)源。下面就講一下在SpringBoot中如何配置多數(shù)據(jù)源躯概。
一、方法介紹
我們使用org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
來(lái)完成數(shù)據(jù)源的切換流纹。在AbstractRoutingDataSource
中Spring使用Map來(lái)管理數(shù)據(jù)源,在對(duì)象初始化完成后會(huì)將配置的對(duì)象放入Map<Object, DataSource> resolvedDataSources
违诗。
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
既然在存儲(chǔ)時(shí)使用到了map漱凝,那么獲取的時(shí)候獲取的時(shí)候我們也得提供這樣一個(gè)key。
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
protected abstract Object determineCurrentLookupKey();
從代碼中我們可以看到较雕,這個(gè)key是由抽象方法determineCurrentLookupKey提供碉哑,所以我們需要重寫這個(gè)方法,提供我們自己生成key的方法亮蒋。
二扣典、實(shí)現(xiàn)多數(shù)據(jù)源選擇器
首先,我們定以一個(gè)枚舉用來(lái)存放數(shù)據(jù)源的key慎玖。
public enum DataSourceType {
// 大數(shù)據(jù)源
BigData,
// 主業(yè)務(wù)源
Business;
}
這里我設(shè)置了BigData
和Business
兩個(gè)key贮尖,分別用來(lái)標(biāo)識(shí)主業(yè)務(wù)數(shù)據(jù)和大數(shù)據(jù)的數(shù)據(jù)源。現(xiàn)在我們繼承AbstractRoutingDataSource
類并重寫determineCurrentLookupKey
方法趁怔。
public class DynamicDataSource extends AbstractRoutingDataSource {
// 數(shù)據(jù)源類型集合
private static final List<DataSourceType> dataSourceTypes = Lists.newArrayList();
//線程本地環(huán)境
private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();
//設(shè)置數(shù)據(jù)源
public static void setDataSourceType(DataSourceType routingType) {
contextHolder.set(routingType);
}
public static void addDataSourceType(DataSourceType dataSourceType) {
if (dataSourceType != null) {
dataSourceTypes.add(dataSourceType);
}
}
public static void reset() {
contextHolder.remove();
}
public static boolean containsDataSource(DataSourceType routingType) {
return dataSourceTypes.contains(routingType);
}
@Override
protected Object determineCurrentLookupKey() {
return contextHolder.get();
}
}
在上面的動(dòng)態(tài)數(shù)據(jù)源中使用ThreadLocal來(lái)存放當(dāng)前的數(shù)據(jù)源類型湿硝,保證每一個(gè)線程都獲取到自己想要的數(shù)據(jù)源類型。下面我們通過(guò)切面來(lái)指定數(shù)據(jù)源润努。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
DataSourceType type();
}
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {
@Around("@annotation(targetDataSource)")
public Object targetDataSource(ProceedingJoinPoint pjp, TargetDataSource targetDataSource) throws Throwable {
DataSourceType dataSourceType = targetDataSource.type();
if (DynamicDataSource.containsDataSource(dataSourceType)) {
DynamicDataSource.setDataSourceType(targetDataSource.type());
}
try {
return pjp.proceed();
} finally {
DynamicDataSource.reset();
}
}
}
對(duì)于使用了TargetDataSource
注解的方法关斜,我們通過(guò)環(huán)繞通知(Around)在方法調(diào)用前設(shè)置ThreadLocal中的數(shù)據(jù)源類型為注解中指定的類型,因?yàn)樘幵诮y(tǒng)一個(gè)線程中铺浇,之后determineCurrentLookupKey
方法就能夠獲取到指定的key痢畜,進(jìn)而得到我們需要的數(shù)據(jù)源。
三鳍侣、配置數(shù)據(jù)源
前面我們已經(jīng)設(shè)置好了多數(shù)據(jù)元切換的選擇器了丁稀,現(xiàn)在我們要提供出多個(gè)數(shù)據(jù)源,并將它們放入到選擇器當(dāng)中倚聚。具體方法如下:
@Configuration
@MapperScan(basePackages = "com.song.study.multidatasource.db.mapper")
@PropertySource(value = "classpath:dataSource.properties")
public class DynamicDataSourceConfig implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private DataSource businessDS;
private DataSource bigDateDS;
@Override
public void setEnvironment(Environment environment) {
businessDS = initDataSource(environment, DataSourceType.BigData.name());
bigDateDS = initDataSource(environment, DataSourceType.Business.name());
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = new HashMap();
targetDataSources.put(DataSourceType.Business, businessDS);
targetDataSources.put(DataSourceType.BigData, bigDateDS);
DynamicDataSource.addDataSourceType(DataSourceType.BigData);
DynamicDataSource.addDataSourceType(DataSourceType.Business);
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
}
public DataSource initDataSource(Environment environment, String prefix) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, prefix);
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setPoolName(prefix + "HikariDataSourcePool");
hikariConfig.setDriverClassName(propertyResolver.getProperty(".database.driverClassName"));
hikariConfig.setJdbcUrl(propertyResolver.getProperty(".database.url"));
hikariConfig.setUsername(propertyResolver.getProperty(".database.username"));
hikariConfig.setPassword(propertyResolver.getProperty(".database.password"));
hikariConfig.setMaxLifetime(propertyResolver.getProperty(".connection.maxLifeTime", Integer.class, 120000));
hikariConfig.setConnectionTimeout(propertyResolver.getProperty(".connection.timeout", Integer.class, 2000));
hikariConfig.setMinimumIdle(propertyResolver.getProperty(".pool.minPoolSize", Integer.class, 20));
hikariConfig.setMaximumPoolSize(propertyResolver.getProperty(".pool.maxPoolSize", Integer.class, 300));
hikariConfig.setConnectionInitSql("SELECT 1");
HikariDataSource dataSource = new HikariDataSource(hikariConfig);
return dataSource;
}
}
四线衫、使用數(shù)據(jù)源
接下來(lái)只要在調(diào)用數(shù)據(jù)庫(kù)操作的方法上添加TargetDataSource
注解就好了。
@Service
public class SampleServiceImpl implements SampleService {
@Autowired
private BusinessRepository businessRepository;
@Autowired
private BigDataRepository bigDataRepository;
@Override
@TargetDataSource(type = DataSourceType.Business)
public BusinessPO getBusiness(Long id) {
return businessRepository.getById(id);
}
@Override
@TargetDataSource(type = DataSourceType.BigData)
public BigDataPO getBigData(Long id) {
return bigDataRepository.getById(id);
}
}
完整example見Github(iceSong/multi-data-source)惑折。