美文网首页SpringBootspring boot
SpringBoot配置多数据源

SpringBoot配置多数据源

作者: 弋炎 | 来源:发表于2017-09-15 19:36 被阅读77次

在最近的开发中需要在业务数据库之外访问大数据提供的数据,所以使用到了多数据源。下面就讲一下在SpringBoot中如何配置多数据源。

一、方法介绍

我们使用org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource来完成数据源的切换。在AbstractRoutingDataSource中Spring使用Map来管理数据源,在对象初始化完成后会将配置的对象放入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);
    }
}

既然在存储时使用到了map,那么获取的时候获取的时候我们也得提供这样一个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();

从代码中我们可以看到,这个key是由抽象方法determineCurrentLookupKey提供,所以我们需要重写这个方法,提供我们自己生成key的方法。

二、实现多数据源选择器

首先,我们定以一个枚举用来存放数据源的key。

public enum DataSourceType {
    // 大数据源
    BigData,
    // 主业务源
    Business;
}

这里我设置了BigDataBusiness两个key,分别用来标识主业务数据和大数据的数据源。现在我们继承AbstractRoutingDataSource类并重写determineCurrentLookupKey方法。

public class DynamicDataSource extends AbstractRoutingDataSource {

    // 数据源类型集合
    private static final List<DataSourceType> dataSourceTypes = Lists.newArrayList();

    //线程本地环境
    private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();

    //设置数据源
    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();
    }
}

在上面的动态数据源中使用ThreadLocal来存放当前的数据源类型,保证每一个线程都获取到自己想要的数据源类型。下面我们通过切面来指定数据源。

@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();
        }
    }
}

对于使用了TargetDataSource注解的方法,我们通过环绕通知(Around)在方法调用前设置ThreadLocal中的数据源类型为注解中指定的类型,因为处在统一个线程中,之后determineCurrentLookupKey方法就能够获取到指定的key,进而得到我们需要的数据源。

三、配置数据源

前面我们已经设置好了多数据元切换的选择器了,现在我们要提供出多个数据源,并将它们放入到选择器当中。具体方法如下:

@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;
    }
}

四、使用数据源

接下来只要在调用数据库操作的方法上添加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)。

相关文章

网友评论

    本文标题:SpringBoot配置多数据源

    本文链接:https://www.haomeiwen.com/subject/qcjesxtx.html