Spring multiple database

최신 JDK, Spring boot에서 JPA와 QueryDSL을 사용할 수 있게 다중 DB 연결하기!

Doohyeon Kim
Doohyeon.kim
9 min readJun 16, 2024

--

이 글을 2월에 작성했는데 검토 후에 올려야지 하다가 워낙 바빠서 검토를 못했다.

이제서야 검토하고 올리자니 요즘은 chatGPT-4o가 나와서 내 글이 크게 도움 될 것 같진 않아서 그냥 올린다.

Java21(JDK 21), Spring Boot 3.2.3, Spring Data JPA 3.0.4, QueryDSL 5.0, 환경에서 작업했다.

Java21이나 Spring Boot 3.x나 나온지 얼마 안돼서 자료가 없어서 고생 좀 했다.

특히 지금 프로젝트가 Aurora(MySQL)와 Tibero를 동시에 연결해야 하는 상황이었는데, 망할 Tibero는 오픈소스도 아니고, 지원 중단된지 한참된 버전을 쓰고 있었다.

JDK랑 Spring Boot는 최신 버전을 쓰면서 왜 티베로를 쓰고 있냐고?

레거시 갈아 엎고 차세대 개발해야 되는데 과도기라서 같이 쓴다…ㅠ_ㅠ

아무튼… 티베로가 핵심은 아니니 티베로 jdbc 연동하는 부분은 제외하고, 다중 DB 연결하는 것에 집중해서 글을 써보겠다.

build.gradle

JPA

implementation 'org.springframework.boot:spring-boot-starter-data-jpa:3.0.4'

queryDSL

implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

그럴 일은 없겠지만, 지원 중단된 Tibero를 쓰고 있는 사람이 있다면, 프로젝트 안에 원하는 위치에 tibero jdbc 파일을 넣어 준 후, 아래 코드를 추가한다.

implementation files('lib/tibero5-jdbc.jar')

Configuration

Data Source의 Configuration 파일을 생성한다. 파일 위치는 프로젝트 내라면 어디든 상관없다. 어차피 스프링은 스프링 컨테이너에 등록하면 다 해주니까.

코드를 먼저 보여주고, 필요한 부분을 설명해주겠다.

TiberoDataSourceConfiguration

설명하는 글이다보니 간단히 TbieroDataSource라고 지었다. 원래는 data source에 맞는 이름으로 작성하는 게 더 좋다.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.domain.service.your_direcotry3",
entityManagerFactoryRef = "tiberoEntityManagerFactory",
transactionManagerRef = "tiberoTransactionManager"
)
@EnableConfigurationProperties
public class TiberoDataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "tibero.datasource")
public DataSourceProperties tiberoDataSourceProperties() {
return new DataSourceProperties();
}

@Bean
public DataSource tiberoDataSource() {
return tiberoDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}

@Bean
public LocalContainerEntityManagerFactoryBean tiberoEntityManagerFactory(
@Qualifier("tiberoDataSource") DataSource dataSource,
EntityManagerFactoryBuilder builder) {
Map<String, String> properties = new HashMap<String, String>();
properties.put("hibernate.dialect", "org.hibernate.dialect.OracleDialect");
properties.put("hibernate.hbm2ddl.auto", "none");
return builder
.dataSource(dataSource)
.properties(properties)
.packages("com.domain.service.your_direcotry3")
.build();
}

@Bean
public PlatformTransactionManager tiberoTransactionManager(
@Qualifier("tiberoEntityManagerFactory") LocalContainerEntityManagerFactoryBean mainEntityManagerFactory) {
return new JpaTransactionManager(Objects.requireNonNull(mainEntityManagerFactory.getObject()));
}
}

@Configuration은 클래스를 설정 클래스로 선언한다. Bean으로 등록되어 스프링 컨테이너에 의해 관리된다.내부를 까보면 @Component가 있다.

@EnableTransactionManagement은 Transaction Manager를 구분하고 @Transational 같은 Transaction 관련 한 것을 허용해주는 어노테이션이다.

@EnableJpaRepositories은 뒤에서 설명하겠다.

@EnableConfigurationProperties는 @ConfigurationProperties 어노테이션이 달린 Bean에 대한 지원을 활성화 한다.

MainDataSourceConfiguration

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = {"com.domain.service.your_direcotry1",
"com.domain.service.your_direcotry2"},
entityManagerFactoryRef = "mainEntityManagerFactory",
transactionManagerRef = "mainTransactionManager")
@EnableConfigurationProperties
public class MainDataSourceConfiguration {

@Primary
@Bean
@ConfigurationProperties(prefix = "main.datasource")
public DataSourceProperties mainDataSourceProperties() {
return new DataSourceProperties();
}

@Primary
@Bean
public DataSource mainDataSource() {
return mainDataSourceProperties()
.initializeDataSourceBuilder()
.build();
}

@Primary
@Bean
public LocalContainerEntityManagerFactoryBean mainEntityManagerFactory(
@Qualifier("mainDataSource") DataSource dataSource,
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dataSource)
.packages("com.domain.service.your_direcotry1",
"com.domain.service.your_direcotry2")
.build();
}

@Primary
@Bean
public PlatformTransactionManager mainTransactionManager(
@Qualifier("mainEntityManagerFactory") LocalContainerEntityManagerFactoryBean mainEntityManagerFactory) {
return new JpaTransactionManager(Objects.requireNonNull(mainEntityManagerFactory.getObject()));
}
}

나는 도메인 분리하는 것을 선호하는 편이라 프로젝트를 도메인 단위로 나누려고 한다. 그럴 때 하나의 data source를 여러 도메인에서 쓰고 싶은 경우 필요한 작업이 두 가지가 있다.

먼저, @EnableJpaRepositories()에서 basePackages 부분에 내가 JPA 저장소를 활성화 시키고 싶은 패키지들의 위치를 넣어준다. 패키지 단위가 아니라 repository 단위여도 괜찮다. 중요한건 @EnableJpaRepositories()가 를 기준으로 탐색한다는 것이다.

그 다음으로 작업할 것은 EntityManagerFactory에 대한 부분인데, JDBC Data Source를 지정할 때 사용한다. EntityManagerFactoryBuilder로 EntityManagerFactory를 생성할 때, packages에 @EnableJpaRepositories()의 basepackages 처럼, Scan하고 싶은 패키지들을 작성한다. 그러면 @Entity 어노테이션이 달린 패키지를 검색한다.

최신 JDK와 패키지를 사용하는데 레퍼런스가 없어서 고생했었던 기억이 나는데, 나 같은 사람들한테 많은 도움이 되면 좋겠다.

--

--

Doohyeon Kim
Doohyeon.kim

Developer, SW Engineer, Product Manager. Expert for startup company.