Spring Data Jpa 에서 한 개의 DB만 사용한다면 application.yml
만 설정해도 auto configuration 을 통해 자동으로 DB 커넥션이 생성된다. 하지만 한 프로젝트에 접근할 DB가 2개 이상이라면? 혹은 다른 DB 서버도 접근해야한다면? 귀찮지만 Configuration 클래스를 생성 해줘야한다.
※ 참고
single datasource 일때 application.yml
설정을 참고하면 좋을 듯하다.
application.yml
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/member
username: sa
password:
hikari:
idle-timeout: 0
maximum-pool-size: 1
jpa:
database-platform: org.hibernate.dialect.H2Dialect
properties:
hibernate.show_sql: true
hibernate.format_sql: true
hibernate.use_sql_comments: true
hibernate.use_query_cache: false
1. application.yml 설정하기
application.yml
에서는 다음과 같이 datasource 설정만 한다.
spring:
member-datasource:
jdbc-url: jdbc:h2:tcp://localhost/~/member
username: sa
password:
idle-timeout: 0
maximum-pool-size: 1
book-datasource:
jdbc-url: jdbc:h2:tcp://localhost/~/book
username: sa
password:
idle-timeout: 0
maximum-pool-size: 1
spring.member-datasource
까지는 변수명을 작성하고 싶은대로 하면 된다. 하지만 그 아래 jdbc-url
같은 속성들은 위 변수명과 똑같이 쓰는 것을 추천한다. 아래에서 확인할 Configuration 클래스에서 DataSource 를 매핑하기가 훨씬 편하기 때문이다.
2. Configuration 클래스 생성하기
일단, 각 datasource 에서 사용할 entity 패키지와 repository package를 분리해줘야한다. 각 Configuration 클래스 마다 패키지 경로를 명시해야하기 때문이다. 필자는 다음과 같이 패키지 구성을 하였다.
전체 코드
MemberDatasource.java
@Configuration
@EnableJpaRepositories(
basePackages = "dev.hyein.jpamultidatasource.repository.member"
, entityManagerFactoryRef = MemberDatasource.ENTITY_MANAGER_FACTORY
, transactionManagerRef = MemberDatasource.TRANSACTION_MANAGER
)
public class MemberDatasource {
public static final String DATA_SOURCE_NAME = "member";
public static final String DATA_SOURCE = DATA_SOURCE_NAME + BeanName.DATA_SOURCE; // memberDataSource
public static final String TRANSACTION_MANAGER = DATA_SOURCE_NAME + BeanName.TRANSACTION_MANAGER; // memberTransactionManager
public static final String ENTITY_MANAGER_FACTORY = DATA_SOURCE_NAME + BeanName.ENTITY_MANAGER_FACTORY; // memberEntityManagerFactory
@Bean(ENTITY_MANAGER_FACTORY)
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(datasource());
em.setPackagesToScan("dev.hyein.jpamultidatasource.entity.member");
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
em.setJpaPropertyMap(
Map.of(
"hibernate.show_sql", "true"
, "hibernate.format_sql", "true"
, "hibernate.use_sql_comments", "true"
, "hibernate.physical_naming_strategy", "org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy"
, "hibernate.use_query_cache", "false"
)
);
return em;
}
@Bean(DATA_SOURCE)
@ConfigurationProperties(prefix = "spring.member-datasource")
public DataSource datasource() {
return DataSourceBuilder.create().build();
}
@Bean(TRANSACTION_MANAGER)
public PlatformTransactionManager transactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return jpaTransactionManager;
}
}
핵심은 @EnableJpaRepositories
이다.
@EnableJpaRepositories(
basePackages = "dev.hyein.jpamultidatasource.repository.member"
, entityManagerFactoryRef = MemberDatasource.ENTITY_MANAGER_FACTORY
, transactionManagerRef = MemberDatasource.TRANSACTION_MANAGER
)
몇 가지 설정을 통해 각 datasource를 JPA Repository 로 접근할 수 있게 해준다.
- basePackages : Repository 클래스가 위치한 패키지 경로
- entityManagerFactoryRef : EntityManagerFactory Bean
- transactionManagerRef : TransactionManager Bean
이제 구성물을 하나하나 파헤쳐보자!
① @Bean(ENTITY_MANAGER_FACTORY)
EntityManagerFactory 를 생성한다.
public static final String DATA_SOURCE_NAME = "member";
public static final String ENTITY_MANAGER_FACTORY = DATA_SOURCE_NAME + BeanName.ENTITY_MANAGER_FACTORY; // memberEntityManagerFactory
@Bean(ENTITY_MANAGER_FACTORY) // [1]
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); // [2]
em.setDataSource(datasource()); // [3]
em.setPackagesToScan("dev.hyein.jpamultidatasource.entity.member"); // [4]
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); // [5]
em.setJpaPropertyMap( // [6]
Map.of(
"hibernate.show_sql", "true"
, "hibernate.format_sql", "true"
, "hibernate.use_sql_comments", "true"
, "hibernate.physical_naming_strategy", "org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy"
, "hibernate.use_query_cache", "false"
)
);
return em;
}
- [1] : Bean name 을
@EnableJpaRepositories
에서도 사용하기 때문에 재사용 가능한 상수로 선언하였다. 다른 데이터소스와 중복되지 않도록 반드시 지정해주는 것이 좋다. - [2] :
LocalContainerEntityManagerFactoryBean
를 생성한다. 이 객체에다가 datasource가 하나였을 때application.yml
에서 했던 다양한 설정들을 적용해 줄 것이다. - [3] :
spring.datasource
와 매핑되는 영역이다. 아래 ② @Bean(DATA_SOURCE) 에서 자세히 알아보자 - [4] :
@Entity
클래스가 위치할 패키지 경로를 선언한다. - [5] :
spring.jpa
와 매핑되는 영역이다. 별도 설정이 없는 경우HibernateJpaVendorAdapter
객체를 바로 생성 후 넣어준다. Dialect의 경우 jdbc-url 을 보고 자동으로 찾아주기 때문에 커스텀 Dialect 가 아닌 이상 굳이 선언해줄 필요 없다.
[6] : [spring.jpa.properties](http://spring.jpa.properties)
와 매핑되는 영역이다. application.yml
에서도 별도 자동완성이 없었듯이 이 부분도 Map 을 통해 key, value 형태로 넣어준다. 이 경우 DB 필드(snake) 와 Entity 필드(camel) 이 자동 매핑이 안돼서 다음을 추가해주었다.
"hibernate.physical_naming_strategy", "org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy"
② @Bean(DATA_SOURCE)
public static final String DATA_SOURCE_NAME = "member";
public static final String DATA_SOURCE = DATA_SOURCE_NAME + BeanName.DATA_SOURCE; // memberDataSource
@Bean(DATA_SOURCE)
@ConfigurationProperties(prefix = "spring.member-datasource")
public DataSource datasource() {
return DataSourceBuilder.create().build();
}
spring.datasource
와 매핑되는 영역이다.
- 내부적으로
HikariDataSource
객체를 사용하기 때문에 기존application.yml
의 hikari connection pool 설정도 가능하다. application.yml
값을@ConfigurationProperties
를 통해 그대로 DataSource 객체와 매핑한다. 부모 클래스인HikariConfig
에서 여러 설정값들을 인스턴스 변수로 받기 때문에 가능한 일이다.- 단,
application.yml
과 클래스가 자동 매핑될 수 있게 yml의 변수명을 인스턴스 변수에 맞춰 생성해야한다. 그게 아닌 yml 변수명을 마음대로 작성하면 매핑이 안돼 null 이 떨어질 것이다. - 어떤 설정값이 존재하는지는 디버깅으로 DataSource 변수를 확인하거나
HikariConfig.java
에서 확인할 수 있다.
- 예외: 변수가 maxPoolSize 라고해서
application.yml
에 max-pool-size: 1 이라고 선언하면 매핑이 안 된다. 왜냐면 내부적으로는 maxPoolSize 를 MaximumPoolSize로 접근하기 때문에 maximum-pool-size: 1 로 선언해줘야한다!
③ @Bean(TRANSACTION_MANAGER)
트랜잭션 관리를 위한 빈이다.
public static final String DATA_SOURCE_NAME = "member";
public static final String TRANSACTION_MANAGER = DATA_SOURCE_NAME + BeanName.TRANSACTION_MANAGER; // memberTransactionManager
@Bean(TRANSACTION_MANAGER)
public PlatformTransactionManager transactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return jpaTransactionManager;
}
PlatformTransactionManager
를 생성 후 위에서 생성한EntityManagerFactory
를 설정해 반환한다.
3. 쿼리 호출하기
이제 multi datasource 설정은 끝났다! entity 와 repository 등 나머지는 기존 방식 그대로 해주면 된다.
Student Entity
package dev.hyein.jpamultidatasource.entity.member;
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "student")
@AllArgsConstructor @NoArgsConstructor @Getter @Setter @ToString
public class Student {
@Id
private Integer id;
private String name;
private String school;
}
Student Repository
package dev.hyein.jpamultidatasource.repository.member;
import dev.hyein.jpamultidatasource.entity.member.Student;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentRepository extends JpaRepository<Student , Integer> {
}
StudentRepositoryTest
@SpringBootTest
class StudentRepositoryTest {
@Autowired
StudentRepository studentRepository;
@Test
@DisplayName("student db 연동 테스트")
void count() {
Assertions.assertNotEquals(0, studentRepository.count());
}
}
ScienceRepositoryTest
@SpringBootTest
class ScienceRepositoryTest {
@Autowired
ScienceRepository scienceRepository;
@Test
@DisplayName("book db 연동 테스트")
void count() {
Assertions.assertNotEquals(0, scienceRepository.count());
}
}
전체 코드는 다음에서 확인할 수 있다.
https://github.com/AwesomeHye/jpa-multidatasource
레퍼런스
[JPA] Multi datasource 설정하기
글 읽어주셔서 언제나 감사합니다. 좋은 피드백, 개선 피드백 너무나도 환영합니다.
'SearchDeveloper > SpringBoot' 카테고리의 다른 글
[JPA] QueryDSL stream DB connection 에러 해결 과정 ② - @Transactional (0) | 2023.01.15 |
---|---|
[JPA] QueryDSL stream DB connection 에러 해결 과정 ① (0) | 2023.01.02 |
[JPA] Querydsl 시작하기 (0) | 2022.10.21 |
[JPA] Querydsl 에서 Fetch Join 적용 안되는 이유 (3) | 2022.10.12 |
Spring REST Docs 이해 (2) | 2022.09.24 |