Spring Boot将多个外部数据源存储在内部数据库中

时间:2019-01-11 08:05:23

标签: java spring spring-boot jpa

我有一个spring boot项目,并且我有一个内部数据库,其中包含application.properties上的配置。在该数据库中,我有一个Company表,其中包含与外部数据库的连接信息(所有外部数据库具有相同的结构)。

我创建了一个在需要时创建数据源的类:

public class PgDataSource {

    private static Map<Long, DataSource> dataSourceMap = new HashMap<>();

    private static void createDataSource(Company company) {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setMaximumPoolSize(10);
        hikariConfig.setMinimumIdle(1);
        hikariConfig.setJdbcUrl("jdbc:postgresql://"+company.getUrl()+"/"+company.getIdClient());
        hikariConfig.setUsername(company.getUsername());
        hikariConfig.setPassword(company.getPassword());

        dataSourceMap.put(company.getId(), new HikariDataSource(hikariConfig));
    }

    public static DataSource getDataSource(Company company) {
        if (!dataSourceMap.containsKey(company.getId()))
            createDataSource(company);

        return dataSourceMap.get(company.getId());
    }

}

您能否告诉我该解决方案是否是最佳解决方案,以及我是否可以在该解决方案中使用JPA?

谢谢

2 个答案:

答案 0 :(得分:0)

设置的难点不是多个数据源,而是它们是动态的,即在运行时确定的事实。 JPA除了使用DataSource之外,还使用EntityManagerFactoryTransactionManager,它们是静态确定的,即在编译时。因此,将JPA与动态数据源一起使用并不容易。

在Spring Boot 2中,您可以尝试使用AbstractRoutingDataSource,它允许基于某些(线程绑定)上下文将JPA调用路由到另一个数据源。 Here's以及如何使用它和demo应用程序的示例。

或者,您可以将设置转换为静态设置,然后使用常规的multiple datasource方法。缺点是“公司”列表将在编译时固定,因此可能不是您想要的。

答案 1 :(得分:0)

谢谢!

我的解决方案:

我使用@Primary注释为我的本地数据库创建了第一个数据源。

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
    entityManagerFactoryRef = "localEntityManagerFactory",
    basePackages = {"fr.axygest.akostaxi.local"}
)
public class LocalConfig {

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

    @Primary
    @Bean(name = "localEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("dataSource") DataSource dataSource) {

        return builder
                .dataSource(dataSource)
                .packages("fr.axygest.akostaxi.local.model")
                .persistenceUnit("local")
                .build();
    }

    @Primary
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("localEntityManagerFactory") EntityManagerFactory
                    entityManagerFactory
    ) {
        return new JpaTransactionManager(entityManagerFactory);
    }

}

接下来,对于保存在本地数据库的company表中的x个外部数据库,我使用AbstractRoutingDataSource

我将当前上下文存储为ThreadLocal:

public class ThreadPostgresqlStorage {

    private static ThreadLocal<Long> context = new ThreadLocal<>();

    public static void setContext(Long companyId) {
        context.set(companyId);
    }

    public static Long getContext() {
        return context.get();
    }

}

我定义了RoutingSource来扩展AbstractRoutingDataSource

public class RoutingSource extends AbstractRoutingDataSource
{
    @Override
    protected Object determineCurrentLookupKey() {
        return ThreadPostgresqlStorage.getContext();
    }

}

然后config类创建保存在company表中的所有数据库连接:

@Configuration
@EnableJpaRepositories(
        basePackages = {"fr.axygest.akostaxi.postgresql"},
        entityManagerFactoryRef = "pgEntityManager"
)
@EnableTransactionManagement
public class PgConfig {

    private final CompanyRepository companyRepository;

    @Autowired
    public PgConfig(CompanyRepository companyRepository) {
        this.companyRepository = companyRepository;
    }

    @Bean(name = "pgDataSource")
    public DataSource pgDataSource() {
        RoutingSource routingSource = new RoutingSource();
        List<Company> companies = companyRepository.findAll();
        HashMap<Object, Object> map = new HashMap<>(companies.size());
        companies.forEach(company -> {
            map.put(company.getId(), createDataSource(company));
        });
        routingSource.setTargetDataSources(map);
        routingSource.afterPropertiesSet();
        return routingSource;
    }

    @Bean(name = "pgEntityManager")
    public LocalContainerEntityManagerFactoryBean pgEntityManager(
            EntityManagerFactoryBuilder builder,
            @Qualifier("pgDataSource") DataSource dataSource) {

        return builder
                .dataSource(dataSource)
                .packages("fr.axygest.akostaxi.postgresql.model")
                .persistenceUnit("pg")
                .properties(jpaProperties())
                .build();
    }

    private DataSource createDataSource(Company company) {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setMaximumPoolSize(10);
        hikariConfig.setMinimumIdle(1);
        hikariConfig.setJdbcUrl("jdbc:postgresql://" + company.getUrl() + "/" + company.getIdClient());
        hikariConfig.setUsername(company.getUsername());
        hikariConfig.setPassword(company.getPassword());

        return new HikariDataSource(hikariConfig);
    }

    private Map<String, Object> jpaProperties() {
        Map<String, Object> props = new HashMap<String, Object>();
        props.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");

        return props;
    }

}