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

9

我有一个比较棘手的问题:

我的情况是:

  • 使用 Spring Data JDBC
  • 使用两个数据库
  • 使用 CrudRepository

正如在这里所看到的那样,在 Spring Data JDBC 中,您可以扩展 CrudRepository 并通过 Spring 轻松地获得所有的 Crud 操作 - 无需显式实现!

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

  1. 定义您的属性
  2. 定义您的实体
  3. 定义扩展了 CrudRepository 的接口,并且
  4. 利用该接口进行操作

但是,在使用两个数据库的情况下,第五步需要定义一个 @Configuration 类。

我按照以下五个步骤进行操作:

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类(每个数据库都需要一个)

  • 你也可以将两个放在同一个类中,但我是按照以下方式进行的:

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。

这导致出现错误:[...]: 未知表名: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 Data文档解释了如何使用多个数据源。你尝试过跟随它们吗? - Roddy of the Frozen Peas
当然可以。你可以在上面的例子中看到。但是,正如你所想象的那样,如果我使用StudentRepositoryTecherRepository方法(如save()delete()等),Spring会查询一个错误的数据库,因为我没有告诉Spring哪个仓库是哪个数据库的。这是一个配置问题。我不知道如何配置它。在Spring JPA中,就像你在这里看到的那样,它非常容易。 - user182410
根据Crig的说法,上面的解决方案对我确实没有用。帖子中提供的链接中有一个解决方案,发布于2021年1月。https://github.com/spring-projects/spring-data-jdbc/issues/544#issuecomment-766664757 - Archana Marathe
4个回答

5

我曾经遇到过类似的问题,我的解决方案是将我的仓库放在两个不同的包中,根据克里斯·萨沃里(Chris Savory)的答案,然后定义两个@Configuration类分别定义一个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 Server

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

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

那么你是运行了两个不同的应用程序,还是一个应用程序启动并连接到两个数据库? - user182410
这是一个连接到两个不同数据库的应用程序。这两个数据源都使用Hikari连接池,我认为这是我需要添加到之前答案中的内容,因为“jdbcUrl”是Hikari特定的属性。 - Twinkie

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

我会从这里开始研究。这就是Spring为一个数据源和一个事务管理器所做的方式。


@Rimdal,感谢您的回答。由于微软将您的答案的电子邮件通知标记为垃圾邮件并放入我的垃圾邮件文件夹中,我看到您的答案太晚了。抱歉。我在上面的帖子中放置了pom.xml。 - user182410

0

类似于Rimidal,我不认为这个方法有效。

这里的文档表明,你需要一个NamedParameterJdbcOperations Bean和(可选)一个TransactionManager Bean。: https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#jdbc.java-config

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

似乎没有办法将不同的NamedParameterJdbcOperations/JDBCTemplates与不同的Repositories关联起来。 在我的测试中,无论将事物隔离到不同的包中并明确告诉@Configuration类使用@EnableJdbcRepositories哪些包,这种方法都不起作用。无论如何,被标记为@Primary的NamedParameterJdbcOperations Bean是所有CrudRepository操作所通过的对象。


事实上,截至2020年底,根据此链接:https://jira.spring.io/browse/DATAJDBC-321,似乎不再支持。 - crig

0
将您的实体和存储库类/接口放入不同的包中。然后,您需要在单独的配置文件中告诉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 {

7
Chris Savory的答案对于Spring Data JPA是正确的,但是对于Spring Data JDBC来说是错误的。在Spring Data JDBC中,并没有使用@EnableJpaRepositories注解,而是只有@EnableJDBCRepositories可用,你可以在官方Spring Ref @EnableJdbcRepositories中看到相关信息。 - user182410

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接