i have a not trivial question:
My Case:
CrudRepository
As you can see here in Spring Data JDBC you can extends CrudRepository
and get with Spring all Crud Operations out of the box - without an explicit implementation!
It's an easy 4 step process for:
But in case of using two databases, there is a 5. Step in which you have to define a @Configuration
class.
I did that these 5 steps as following:
<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>
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=...
Student.java // for db1
@Table("STUDENT_TABLE")
public class Student{
@Id
@Column("MAT_NR")
private BigDecimal matNr;
@Column("NAME")
private String name;
}
Teacher.java // for db2
@Table("TEACHER_TABLE")
public class Teacher{
@Id
@Column("EMPLOYEE_NR")
private BigDecimal employeeNr;
@Column("NAME")
private String name;
}
StudentRepository.java // for DB1
@Repository
public interface StudentRepository extends CrudRepository<Student, BigDecimal> {}
TeacherRepository.java // for DB2
@Repository
public interface TeacherRepository extends CrudRepository<Teacher, BigDecimal> {}
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();
}
}
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
}
}
These is working fine!
TeacherRepository
not queries DB2, it queries DB1.which results in an Error: [...]: Unknown table name:TEACHER
.
# Please note before you Answer:
Here i'm using Spring Data JDBC and not Spring Data JPA. I know that it works in Spring Data JPA like described here 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 and that’s not what need.
An answer would be nice of course.
Thanks for your help.
I had a similar problem. My solution had to have my repositories put in 2 separate packages, as per Chris Savory answer, and then define 2 @Configuration classes defining 1 JdbcOperation each. Here's my full configuration (I have an SQL Server and an H2 data sources):
application.properties
Please note that these properties are Hikari CP specific. Mileage may vary if you chose a different CP (i.e. 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
First 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;
}
}
Second 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);
}
}
Then I have my repositories (please note the different packages).
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);
}
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);
}