목차
개요
Spring 내에서 다중 DB를 사용하고 싶으면 다중 DB 설정을 진행해야한다고 합니다. 기존에 사용하듯이 application.yml과 같은 설정 파일에 하나의 DB만 설정하면 Spring Boot에서 자동 구성(Auto Configuration)을 통해 문제 없이 사용 할 수 있었지만 다중 DB 설정에는 자동 구성이 되지 않기 때문에 설정 파일 값을 읽어와 연동 할 DB 수 만큼 Datasource를 수동 설정해야한다고 합니다.
application.yml 설정
아래와 같이 application.yml을 설정하였습니다.
second-datasource는 본인 원하는대로 이름을 붙이면 됩니다. (예를 들면, second.datasource)
Datasource 설정에서 주입해줄 것이기 때문에 상관없습니다.
spring:
# primary datasource
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/testdb
username: sa
password: 1234
# second datasource
second-datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://localhost:3307/testdb
username: sa
password: 1234
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
Datasource 설정
application.yml 설정을 마치면 Datasource 설정을 진행합니다. 2개의 Datasource를 만드려고 합니다. 첫 번째 Datasource는 PrimaryDatasource, 두 번째 Datasource는 SecondDatasource라고 부르도록 하겠습니다. 설정과 관련된 코드를 살펴보겠습니다. (주석으로 간단한 설명을 첨부하겠습니다.)
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.multipleDB.repositoryConfig.primary", // 첫번째 Repository가 있는 패키지 경로
entityManagerFactoryRef = "primaryEntityManagerFactory", // EntityManager 이름
transactionManagerRef = "primaryTransactionManager" // 트랜잭션 매니저 이름
)
public class PrimaryDatasourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource") // application.yml에 작성된 첫 번째 DB 설정의 시작 부분
public DataSourceProperties primaryDatasourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("spring.datasource.configuration") // application.yml에 작성된 첫 번째 DB 설정의 시작 부분에 .configuration을 붙여준다.
public DataSource primaryDatasource() {
return primaryDatasourceProperties()
.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
}
@Bean(name = "primaryEntityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(EntityManagerFactoryBuilder builder) {
DataSource dataSource = primaryDatasource();
return builder
.dataSource(dataSource)
.packages("com.multipleDB.member") // 스캔이 필요한 패키지 경로
.persistenceUnit("primaryEntityManager")
.build();
}
@Bean(name = "primaryTransactionManager")
@Primary
public PlatformTransactionManager primaryTransactionManager(final @Qualifier("primaryEntityManagerFactory") LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean){
return new JpaTransactionManager(localContainerEntityManagerFactoryBean.getObject());
}
}
위와 같이 4개의 Bean을 만들어주면 된다고 합니다.
첫 번째 DB 설정에는 @Primary를 붙여야지만 그 이외의 DB 설정에서는 @Primary를 빼주어야 합니다. 맨 처음 코드에서 Class 이름은 'PrimaryDatasource'라고 짓는 실수를 했었습니다. 해당 실수의 결과로 아래의 에러 로그를 만났었습니다.
위 사진에 나와있는 로그대로 overriding을 허가해주는 방법으로 해결하려 했지만 여전히 아래의 에러 로그로 문제가 생겼었습니다.
확인해본 결과 Class 이름과 job 이름이 동일해서 발생한 오류라고하여 클래스 이름을 'PrimaryDatasourceConfig'로 변경하여 해결했습니다.
(또한 overriding을 허가해주던 설정을 삭제하였습니다.)
두 번째 Datasource도 동일한 방법으로 진행하면 됩니다.
두 번째 Datasource에 대한 코드는 테스트 과정에서 발생한 이슈를 해결한 코드와 함께 보여드리도록 하겠습니다.
테스트 진행
이슈 발생
설정을 완료한 후 무사히 진행되는지 확인하기 위해 간단한 Member 클래스와 관련 Controller, Service 클래스를 생성한 후, Postman을 통해 member가 잘 생성되는지 확인하려고 했습니다.
테스트를 진행하기 위해 생성된 데이터베이스 중 MariaDB 쪽은 member 테이블을 생성하지 않았기에 ddl-auto 설정에 따라 member 테이블 생성하는 쿼리가 아래 사진과 같이 발생하여야 하는데 아무런 쿼리가 발생하지 않았고 확인해본 결과 member 테이블을 생성되지 않았음을 확인하였습니다.
이슈의 원인
이슈의 원인은 jpa, hibernate 설정을 datasource에 넣어주지 않아서였습니다. 아래와 같은 코드를 datasource 설정 클래스에 추가해주어야 했습니다.
@RequiredArgsConstructor
...
public class SecondDataSourceConfig {
private final JpaProperties jpaProperties;
private final HibernateProperties hibernateProperties;
@Bean(name = "primaryEntityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(EntityManagerFactoryBuilder builder) {
Map<String, Object> properties = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
DataSource dataSource = primaryDatasource();
return builder
.dataSource(dataSource)
.packages("com.multipleDB.member") // 스캔이 필요한 패키지 경로
.persistenceUnit("primaryEntityManager")
.properties(properties)
.build();
}
...
}
위와 같이 설정해주어야 application.yml에서 설정한 JPA, hibernate 설정 값들이 적용된다고 합니다. 또한 JPA의 Naming Strategy도 위와 같은 설정이 있어야 정상적으로 작동한다고 합니다.
Naming Strategy란?
원래 JPA의 기본 설정상으로는 변수명이 camelCase로 작성되어 있으면 DB의 테이블이나 필드 이름이 snake_case로 매칭되도록 합니다. camelCase를 SNAKE_CASE로 변경하는 등 변화를 줄 수 있는데 이러한 전략들을 JPA의 Naming Strategy라고 합니다. 조직 내부의 약속대로 설정하는 법을 알고 싶다면 아래의 블로그 글을 참조해주세요.
https://velog.io/@mumuni/Hibernate5-Naming-Strategy-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC
https://velog.io/@devduhan/Spring%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-JPA-Naming-%EC%A0%84%EB%9E%B5
이슈 해결 내용을 적용한 Datasource
앞선 이슈를 해결한 Datasource 코드를 공유드리도록 하겠습니다.
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.multipleDB.repositoryConfig.second",
entityManagerFactoryRef = "secondEntityManagerFactory",
transactionManagerRef = "secondTransactionManager"
)
@RequiredArgsConstructor
public class SecondDatasourceConfig {
// jpa, hibernate property 값 주입하기 위해
private final JpaProperties jpaProperties;
private final HibernateProperties hibernateProperties;
@Bean
@ConfigurationProperties("spring.second-datasource")
public DataSourceProperties secondDatasourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("spring.second-datasource.configuration")
public DataSource secondDatasource() {
return secondDatasourceProperties()
.initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
}
@Bean(name = "secondEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(EntityManagerFactoryBuilder builder) {
DataSource dataSource = secondDatasource();
Map<String, Object> properties = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings());
return builder
.dataSource(dataSource)
.packages("com.multipleDB.member")
.persistenceUnit("secondEntityManager")
.properties(properties)
.build();
}
@Bean(name = "secondTransactionManager")
public PlatformTransactionManager secondTransactionManager(final @Qualifier("secondEntityManagerFactory") LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean) {
return new JpaTransactionManager(localContainerEntityManagerFactoryBean.getObject());
}
}
위와 같이 설정을 완료한 뒤 다시 코드를 실행하니 테이블 생성 쿼리문도 무사히 출력되었고 확인해본 결과 member 테이블이 잘 생성되었음을 확인할 수 있었습니다.
또한 Postman으로 요청을 보낸 결과 무사히 데이터가 들어갔음을 확인 할 수 있었습니다.
Reference
https://velog.io/@lehdqlsl/SpringBoot-JPA-Multiple-Databases-%EC%84%A4%EC%A0%95