2 回答

TA贡献1817条经验 获得超14个赞
设置的困难不是多个数据源,而是它们是动态的,即在运行时确定的事实。除了DataSource
JPA 使用EntityManagerFactory
和TransactionManager
,它们是静态确定的,即在编译时。因此,将 JPA 与动态数据源一起使用并不容易。
在 Spring Boot 2 中,您可以尝试AbstractRoutingDataSource,它允许基于某些(线程绑定)上下文将 JPA 调用路由到不同的数据源。这是一个如何使用它的示例和一个演示应用程序。
或者,您可以将设置转换为静态设置,然后使用常规的多数据源方法。缺点是“公司”列表将在编译时固定,因此可能不是您想要的。

TA贡献1777条经验 获得超10个赞
我的解决方案:
我使用@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();
}
}
配置类创建保存在公司表中的所有数据库连接:
@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;
}
}
添加回答
举报