Spring JPA – 具有相同存储库的多个数据库

Spring JPA – Multiple Databases with the same Repositories

我已按照此示例配置 Spring JPA 以使用多个数据源。 Spring JPA – Multiple Databases

它按预期工作,但由于我想使用不同的数据源但使用相同的存储库,所以出现了问题。当我尝试在我的服务上使用“声明式事务管理”并指定我将使用主要事务或次要事务时,事务注释忽略了它。所以在这种情况下,它使用的是第二个。但是,创建了两个 bean“PlatformTransactionManager”,但是当涉及到在“@Transactional”中使用时,我无法使事务与我指定的 bean 一起工作。因此,@Transactional 似乎忽略了 bean 名称,因为它们具有相同的存储库。有什么办法可以让我在尝试时使用声明性交易吗?正如我所看到的,它可以通过程序化事务管理来完成,但由于我一直只使用声明式事务,因此更改服务的整个代码会花费我很多钱。

在我们的例子中,我们还有两个数据库,都使用 JPA 和存储库。但是,他们确实使用不同的存储库,但我认为我们的方法也适用于您的情况。我们为每个数据库定义一个 EntityManagerFactory 和一个 TransactionManager。我无法确认这是否有效,但我相信它应该有效。

主要数据源:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = {"ch.sac.data.primary.repository"},
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager")
public class ImporterPrimaryDataSourceConfiguration {

    @Primary
    @Bean(name = "dataSourceProperties")
    @ConfigurationProperties("spring.datasource")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    @Primary
    @Bean(name = "primaryDataSource")
    public DataSource dataSource(final DataSourceProperties dataSourceProperties) {
        return dataSourceProperties
                .initializeDataSourceBuilder()
                .type(HikariDataSource.class)
                .build();
    }

    @Primary
    @Bean
    public Flyway primaryFlyway(
            final DataSource dataSource,
            @Value("${spring.flyway.locations}") final String[] flywayLocations,
            @Value("${spring.flyway.validate-on-migrate:true}")
                    final boolean flywayValidateOnMigrate) {
        final var flyway =
                Flyway.configure()
                        .dataSource(dataSource)
                        .locations(flywayLocations)
                        .validateOnMigrate(flywayValidateOnMigrate)
                        .load();
        flyway.migrate();
        return flyway;
    }

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            final EntityManagerFactoryBuilder entityManagerFactoryBuilder,
            final DataSource dataSource) {

        final var jpaProperties =
                Map.ofEntries(
                        Map.entry(
                                "hibernate.dialect",
                                "org.hibernate.spatial.dialect.postgis.PostgisDialect"));

        return entityManagerFactoryBuilder
                .dataSource(dataSource)
                .packages("ch.sac.model.primary.entity")
                .persistenceUnit("dataSource")
                .properties(jpaProperties)
                .build();
    }

    @Primary
    @Bean
    public PlatformTransactionManager transactionManager(
            final EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

辅助数据源(SQLite 数据库,已创建 in-memory,调整为您的 use-case):

@Configuration
@EnableJpaRepositories(
        basePackages = {"ch.sac.data.sqlite.repository"},
        entityManagerFactoryRef = "sqliteEntityManagerFactory",
        transactionManagerRef = "sqliteTransactionManager")
public class ImporterSQLiteDataSourceConfiguration {

    @Bean(name = "sqliteDBFilePath")
    public String sqliteDBFilePath() throws IOException {
        return TempFileManager.createEmptyTempFile("metadata.sqlite").toString();
    }

    @Bean(name = "sqliteDataSourceProperties")
    public DataSourceProperties sqliteDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean(name = "sqliteDataSource")
    public DataSource sqliteDataSource(
            @Qualifier("sqliteDataSourceProperties")
                    final DataSourceProperties sqliteDataSourceProperties,
            final String sqliteDBFilePath) {
        return sqliteDataSourceProperties
                .initializeDataSourceBuilder()
                .driverClassName("org.sqlite.JDBC")
                .url("jdbc:sqlite:" + sqliteDBFilePath)
                .type(HikariDataSource.class)
                .build();
    }

    @Bean
    public Flyway sqliteFlyway(
            @Qualifier("sqliteDataSource") final DataSource sqliteDataSource,
            @Value("${sqlite.flyway.locations}") final String[] flywayLocations) {
        final var flyway =
                Flyway.configure().dataSource(sqliteDataSource).locations(flywayLocations).load();
        flyway.migrate();
        return flyway;
    }

    @Bean(name = "sqliteEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean sqliteEntityManagerFactory(
            final EntityManagerFactoryBuilder sqliteEntityManagerFactoryBuilder,
            @Qualifier("sqliteDataSource") final DataSource sqliteDataSource) {

        final var sqliteJpaProperties =
                Map.ofEntries(
                        Map.entry(
                                "hibernate.dialect", "org.sqlite.hibernate.dialect.SQLiteDialect"));

        return sqliteEntityManagerFactoryBuilder
                .dataSource(sqliteDataSource)
                .packages("ch.sac.model.sqlite.entity")
                .persistenceUnit("sqliteDataSource")
                .properties(sqliteJpaProperties)
                .build();
    }

    @Bean(name = "sqliteTransactionManager")
    public PlatformTransactionManager sqliteTransactionManager(
            @Qualifier("sqliteEntityManagerFactory")
                    final EntityManagerFactory sqliteEntityManagerFactory) {
        return new JpaTransactionManager(sqliteEntityManagerFactory);
    }
}

找到答案:可以通过扩展 spring 的 AbstractRoutingDataSource class 来完成。

理想情况下,最好有两个独立的微服务连接到两个不同的数据源。