使用 Spring Data JDBC 和 CrudRepository 接口的多个数据源

Posted

技术标签:

【中文标题】使用 Spring Data JDBC 和 CrudRepository 接口的多个数据源【英文标题】:Multiple DataSources using Spring Data JDBC and CrudRepository Interface 【发布时间】:2020-03-16 12:35:06 【问题描述】:

我有一个不重要的问题:

我的情况:

使用 Spring Data JDBC 使用两个数据库 CrudRepository 的用法

您可以在 Spring Data JDBC 中看到here,您可以通过extends CrudRepository 使用 Spring 开箱即用所有 Crud 操作 - 无需显式实现!

这是一个简单的 4 步过程:

    定义您的属性 定义您的实体 定义一个扩展 CrudRepository 的接口和 使用该界面

但在使用两个数据库的情况下,有一个 5. 步骤,您必须在其中定义一个@Configuration 类。

我做了以下 5 个步骤:

0。 Pom.xml

 <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>

1。定义你的属性

application.properties

## D1
datasource.db1.driverClassName=...
datasource.db1.username=...
datasource.db1.password=...
datasource.db1.jdbcUrl=...
## D2
datasource.db2.driverClassName=...
datasource.db2.username=...
datasource.db2.password=...
datasource.db2.jdbcUrl=...

2。定义您的实体(每个数据库一个)

Student.java // 用于 db1

@Table("STUDENT_TABLE")
public class Student
    @Id
    @Column("MAT_NR")
    private BigDecimal matNr;

    @Column("NAME")
    private String name;

Teacher.java // 用于 db2

@Table("TEACHER_TABLE")
public class Teacher
    @Id
    @Column("EMPLOYEE_NR")
    private BigDecimal employeeNr;

    @Column("NAME")
    private String name;

3。定义您的存储库(每个数据库一个)

StudentRepository.java // 对于 DB1

@Repository
public interface StudentRepository extends CrudRepository<Student, BigDecimal> 

TeacherRepository.java // 对于 DB2

@Repository
public interface TeacherRepository extends CrudRepository<Teacher, BigDecimal> 

4。定义您的 @Configuration 类(每个 DB 一个)

你也可以在一门课上两门课,但我是这样做的:

Db1Config.java

@Configuration
public class Db1Config 
    @Primary
    @Bean("db1DataSource")
    @ConfigurationProperties("datasource.db1")
    public DataSource db1DataSource() 
        return DataSourceBuilder.create().build();
    

Db2Config.java

@Configuration
public class Db2Config 
    @Bean("db2DataSource")
    @ConfigurationProperties("datasource.db2")
    public DataSource db2DataSource() 
        return DataSourceBuilder.create().build();
    

5。使用您的接口存储库

Application.java

@SpringBootApplication
public class Application implements CommandLineRunner 

    @Autowired @Qualifier("studentRepository") StudentRepository studentRepository
    @Autowired @Qualifier("teacherRepository") TeacherRepository teacherRepository 

    public static void main(String[] args) 
        SpringApplication.run(Application.class, args);
    

    @Override
    public void run(String... args) throws Exception 
        studentRepository.findById(30688).ifPresent(System.out::println); // DB1
        teacherRepository.findById(5).ifPresent(System.out::println); // DB2
    

这些工作正常!

这里的问题是,TeacherRepository 不是查询 DB2,而是查询 DB1。

导致错误:[...]: Unknown table name:TEACHER

有谁知道我可以如何配置 TeacherRepository 使用 DB2 作为数据源?

#回答前请注意:

这里我使用的是 Spring Data JDBC 而不是 Spring Data JPA。我知道它可以在 Spring Data JPA 中工作,就像这里描述的 https://www.baeldung.com/spring-data-jpa-multiple-databases 一样。我也知道我可以使用这些JdbcTemplate。但是那样的话,我必须自己编写这些 CRUD 操作,描述为 here,这不是我们需要的。

答案当然很好。

感谢您的帮助。

【问题讨论】:

spring 数据文档解释了如何使用多个数据源。你试过关注他们吗? 当然可以。您可以在上面的示例中看到这一点。但是你可以想象,如果我使用StudentRepsositoryTecherRepository 方法(如save(),或delete(),...)spring 查询数据库,但错误的,因为我没有告诉spring 哪个存储库用于哪个数据库。这是一个配置问题。我不知道如何配置它。在 Spring JPA 中是否如您所见here 如此简单 根据 Crig 的说法,上面的解决方案确实对我不起作用。帖子中提供的链接在 2021 年 1 月发布了解决方案 github.com/spring-projects/spring-data-jdbc/issues/… 【参考方案1】:

我遇到了类似的问题。根据 Chris Savory 的回答,我的解决方案必须将我的存储库放入 2 个单独的包中,然后定义 2 个 @Configuration 类,每个类定义 1 个 JdbcOperation。 这是我的完整配置(我有一个 SQL Server 和一个 H2 数据源):

application.properties

请注意,这些属性是 Hikari CP 特定的。如果您选择不同的 CP(即 Tomcat),里程可能会有所不同

## SQL SERVER DATA SOURCE
spring.sql-server-ds.jdbcUrl= jdbc:sqlserver://localhost:1554;databaseName=TestDB
spring.sql-server-ds.username= uteappl
spring.sql-server-ds.password= mypassword

## H2 DATA SOURCE
spring.h2-ds.jdbcUrl= jdbc:h2:mem:testdb;mode=mysql
spring.h2-ds.username= sa
spring.h2-ds.password= password

第一个 H2 @Configuration

@Configuration
@EnableJdbcRepositories(jdbcOperationsRef = "h2JdbcOperations", basePackages = "com.twinkie.repository.h2")
public class H2JdbcConfiguration extends AbstractJdbcConfiguration 


  @Bean
  @ConfigurationProperties(prefix = "spring.h2-ds")
  public DataSource h2DataSource() 
    return DataSourceBuilder.create().build();
  


  @Bean
  NamedParameterJdbcOperations h2JdbcOperations(@Qualifier("h2DataSource") DataSource sqlServerDs) 
    return new NamedParameterJdbcTemplate(sqlServerDs);
  

  @Bean
  public DataSourceInitializer h2DataSourceInitializer(
      @Qualifier("h2DataSource") final DataSource dataSource) 
    ResourceDatabasePopulator resourceDatabasePopulator = new ResourceDatabasePopulator(
        new ClassPathResource("schema.sql"));
    DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
    dataSourceInitializer.setDataSource(dataSource);
    dataSourceInitializer.setDatabasePopulator(resourceDatabasePopulator);
    return dataSourceInitializer;
  

第二个 SQL Server @Configuration

@Configuration
@EnableJdbcRepositories("com.twinkie.repository.sqlserver")
public class SqlServerJdbcConfiguration 

  @Bean
  @Primary
  @ConfigurationProperties(prefix = "spring.sql-server-ds")
  public DataSource sqlServerDataSource() 
    return DataSourceBuilder.create().build();
  

  @Bean
  @Primary
  NamedParameterJdbcOperations jdbcOperations(
      @Qualifier("sqlServerDataSource") DataSource sqlServerDs) 
    return new NamedParameterJdbcTemplate(sqlServerDs);
  


然后我有我的存储库(请注意不同的包)。

SQL 服务器

package com.twinkie.repository.sqlserver;

import com.twinkie.model.SoggettoAnag;
import java.util.List;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;

public interface SoggettoAnagRepository extends CrudRepository<SoggettoAnag, Long> 

  @Query("SELECT * FROM LLA_SOGGETTO_ANAG WHERE sys_timestamp > :sysTimestamp ORDER BY sys_timestamp ASC")
  List<SoggettoAnag> findBySysTimestampGreaterThan(Long sysTimestamp);

H2

package com.twinkie.repository.h2;

import com.twinkie.model.GlSync;
import java.util.Optional;
import org.springframework.data.jdbc.repository.query.Modifying;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.Repository;

public interface GlSyncRepository extends Repository<GlSync, String> 

  @Modifying
  @Query("INSERT INTO GL_SYNC (table_name, last_rowversion) VALUES (:tableName, :rowVersion) ON DUPLICATE KEY UPDATE last_rowversion = :rowVersion")
  boolean save(String tableName, Long rowVersion);

  @Query("SELECT table_name, last_rowversion FROM gl_sync WHERE table_name = :tableName")
  Optional<GlSync> findById(String tableName);

【讨论】:

那么您有两个不同的应用程序正在运行,或者它是一个应用程序启动并连接到两个数据库? 这是一个连接到 2 个不同数据库的应用程序。两个 ds 都使用 Hikari 连接池,我认为这是我需要添加到我之前的答案中的东西,因为“jdbcUrl”是 Hikari 特有的属性。【参考方案2】:

我认为您几乎完成了配置,但我认为缺少一部分。您创建 Db1ConfigDb2Config 并区分它们。但是spring怎么知道用什么,用在哪里。我的猜测是:您必须提供两个 TransactionManagers(我用于相同的问题)并连接存储库(适当的)。如果 TransactionManager 不在 @EnableJDVCRepositories 中,请提供有关您的代码的更多信息(pom.xml?),我几乎可以肯定您必须至少再创建两个 bean。

我将从here 开始研究。这就是spring对一个数据源和一个事务管理器的处理方式。

【讨论】:

@Rimdal 感谢您的回答。由于微软将您的答案的电子邮件通知作为垃圾邮件放在我的垃圾邮件文件夹中,我看到您的答案很晚。对不起。我把 pom.xml 放在我上面的帖子里。【参考方案3】:

将您的实体和存储库类/接口放入不同的包中。然后你需要告诉 Spring Jpa 在你的单独配置文件中扫描这些包的位置

@EnableJpaRepositories(basePackages =  "com.yourpackage.repositories1" ,
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager")
@Configuration
public class Db1Config 
@EnableJpaRepositories(basePackages =  "com.yourpackage.repositories2" ,
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager")
@Configuration
public class Db2Config 

【讨论】:

Chris Savory 对于Spring Data JPA,您的答案是正确的,但对于Spring Data JDBC,它是错误的。在Spring Data JDBC 中不是注释@EnableJpaRepositories,只有@EnableJDBCRepositories 是可用的,正如您在Official Spring Ref for [@EnableJdbcRepositories 中看到的那样【参考方案4】:

与 Rimidal 类似,我认为这行不通。 这里的文档表明您需要一个 NamedParameterJdbcOperations Bean 和(可选)一个 TransactionManager Bean。: https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#jdbc.java-config

NamedParameterJdbcOperations 是 CrudRepositories 用来访问数据库的 JDBCTemplate。

似乎没有办法将不同的 NamedParameterJdbcOperations/JDBCTemplates 与不同的存储库相关联。 在我的测试中,这无论如何都不起作用。无论哪个 NamedParameterJdbcOperations Bean 被标记为 @Primary 都是所有 CrudRepository 操作都被馈送的,不管将事物分离到不同的包中并明确告诉@Configuration 类哪些包与@EnableJdbcRepositories 一起使用。

【讨论】:

事实上,截至 2020 年底,这似乎不受支持:jira.spring.io/browse/DATAJDBC-321

以上是关于使用 Spring Data JDBC 和 CrudRepository 接口的多个数据源的主要内容,如果未能解决你的问题,请参考以下文章

spring-data-jdbc 中是不是有相当于@BatchSize

使用 Spring Data JDBC 和 CrudRepository 接口的多个数据源

使用 spring-data-jpa 和完整的 tomcat-jdbc 配置的 spring boot 为 ResetAbandonedTimer 抛出 java.lang.ClassNotFound

Spring Data JDBC - 如何使用自定义 ID 生成

Spring Data JPA 中的 JDBC CommunicationsException

Spring Data JDBC / Spring Data JPA 与 Hibernate