使用 Spring 数据 JDBC 和 CrudRepository 接口的多个数据源
Multiple DataSources using Spring Data JDBC and CrudRepository Interface
我有一个不重要的问题:
我的案例:
- 使用Spring数据JDBC
- 使用两个数据库
CrudRepository
的用法
正如您在 Spring 中看到的 here 数据 JDBC 您可以 extends CrudRepository
并获得 Spring 所有开箱即用的 Crud 操作 - 没有明确的实施!
这是一个简单的 4 步过程:
- 定义您的属性
- 定义您的实体
- 定义一个扩展 CrudRepository 和
的接口
- 使用该接口
但是在使用两个数据库的情况下,有一个 5. 步骤,您必须在其中定义 @Configuration
class.
我按照以下 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 class(每个数据库一个)
- 你也可以把两者合二为一class但我是这样做的:
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 数据 JDBC 而不是 Spring 数据 JPA .我知道它在 Spring Data JPA 中工作,就像这里描述的那样 https://www.baeldung.com/spring-data-jpa-multiple-databases. I know also that i can make usage of these JdbcTemplate
. But in that way, i have to write these CRUD Operations by myself which is described here 而这不是需要的。
当然最好有答案。
感谢您的帮助。
将您的实体和存储库 classes/interfaces 放入不同的包中。然后你需要告诉 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 {
我认为您几乎完成了配置,但我认为缺少一部分。您创建 Db1Config 和 Db2Config 并区分它们。但是 spring 怎么知道在什么地方使用什么。我的猜测是:您必须提供两个 TransactionManagers(我用于相同的问题)和连接存储库(适当的)。如果 TransactionManager 不在 @EnableJDVCRepositories 中,请提供更多关于您的代码的信息(pom.xml?)我几乎可以肯定您必须创建至少还有两颗豆子。
我将从 here 开始研究。这就是 spring 为一个数据源和一个事务管理器执行此操作的方式。
我遇到了类似的问题。根据 Chris Savory 的回答,我的解决方案必须将我的存储库放入 2 个单独的包中,然后定义 2 个 @Configuration 类,每个定义 1 个 JdbcOperation。
这是我的完整配置(我有一个 SQL 服务器和一个 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 服务器 @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);
}
与 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 一起使用。
我有一个不重要的问题:
我的案例:
- 使用Spring数据JDBC
- 使用两个数据库
CrudRepository
的用法
正如您在 Spring 中看到的 here 数据 JDBC 您可以 extends CrudRepository
并获得 Spring 所有开箱即用的 Crud 操作 - 没有明确的实施!
这是一个简单的 4 步过程:
- 定义您的属性
- 定义您的实体
- 定义一个扩展 CrudRepository 和 的接口
- 使用该接口
但是在使用两个数据库的情况下,有一个 5. 步骤,您必须在其中定义 @Configuration
class.
我按照以下 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 class(每个数据库一个)
- 你也可以把两者合二为一class但我是这样做的:
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 数据 JDBC 而不是 Spring 数据 JPA .我知道它在 Spring Data JPA 中工作,就像这里描述的那样 https://www.baeldung.com/spring-data-jpa-multiple-databases. I know also that i can make usage of these JdbcTemplate
. But in that way, i have to write these CRUD Operations by myself which is described here 而这不是需要的。
当然最好有答案。
感谢您的帮助。
将您的实体和存储库 classes/interfaces 放入不同的包中。然后你需要告诉 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 {
我认为您几乎完成了配置,但我认为缺少一部分。您创建 Db1Config 和 Db2Config 并区分它们。但是 spring 怎么知道在什么地方使用什么。我的猜测是:您必须提供两个 TransactionManagers(我用于相同的问题)和连接存储库(适当的)。如果 TransactionManager 不在 @EnableJDVCRepositories 中,请提供更多关于您的代码的信息(pom.xml?)我几乎可以肯定您必须创建至少还有两颗豆子。
我将从 here 开始研究。这就是 spring 为一个数据源和一个事务管理器执行此操作的方式。
我遇到了类似的问题。根据 Chris Savory 的回答,我的解决方案必须将我的存储库放入 2 个单独的包中,然后定义 2 个 @Configuration 类,每个定义 1 个 JdbcOperation。 这是我的完整配置(我有一个 SQL 服务器和一个 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 服务器 @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);
}
与 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 一起使用。