JPA, JDBC의 DB MultiDataSource 적용하기
JPA와 JDBC를 동시에 쓰고 싶다!
JPA 그리고 JDBC 를 동시에 사용하는 방법은 그렇게 어렵지 않다. JPA 의존성 밑에 JDBC가 포함되어있어서 JDBC template를 이미 사용할 수 있는 구조로 되어있어서 의존성을 또 추가하지 않아도 되는 구조로 구성되어있다. 그래서 JPA 와 JDBC를 통해서 코드를 구성하는 편이 훨씬 편한 방식으로 코드를 짜는데 더 편리하다.
왜냐하면 JPA를 사용하면서 Mybatis와 같은 다른 형식의 코드를 사용하려면 그것에 맞는 형식을 또 따로 잡아줘야하는데 JDBC는 결국 의존성이 포함되어있는 형식으로 되어있어서, 큰틀은 변하지 않는다는 점을 잘 활용하는 것이 좋다.
문제는 이 두개를 동시에 다른 DB를 사용하는 경우 간단한 세팅으로는 문제 해결이 재대로 진행되지 않는다는 점이다.
핵심 문제는?
DB가 다른 경우 JPA와 JDBC가 모두 다른 DB를 사용해야하는 경우나 JPA를 단독으로 사용하더라도 2개의 DB를 사용하는 케이스에 DB를 적용하는 경우에 대해서 적용하는 경우를 적어보도록 하겠다.
대부분의 블로그의 경우 Mybatis + JPA , JPA 2개의 경우에 대해서만 적고 있어서 나는 JDBC, JPA에 대한 경우를 모두 포함해서 작성해보겠다.
기본적으로 DB를 한개 포함하는 경우에서는 우리는 DB 설정을 Application.yml 혹은 properties에서 다음과 같이 설정했을 것이다.
spring:
h2:
console:
enabled: false
jpa:
show-sql: true
hibernate:
ddl-auto: none
datasource:
url: DB주소
username: DB 유저 이름
password: DB 유저 비밀번호
문제는 DB가 두개인 경우에는 이렇게 사용하면 default에서 2개의 DB를 인식하지 못하고, 한개만 처리해서 내부의 DB url을 하나로만 처리한다. 즉, 완전히 다르게 코드를 구성해야한다.
2개 이상의 DB DataSource를 사용하는 법
우리는 Configuration 파일을 각 DB별로 따로 설정해줘야한다. 즉, 우리는 이 Configuration파일을 통해서 DB의 Datasource를 직접 집어넣는 방식으로 적용해야한다. 이전의 방식으로는 불가능하다.
이제 Yaml 파일는 사실상 값을 가지는 설정파일로써의 역할로 내가 yaml파일에서 class로 읽어들여오는 방식으로 코드를 가져와야한다.
우리는 DB Configuration을 설정하려면 몇몇 조건을 설정해야한다. 이것도 좀 웃긴게 JDBC일때와 JPA일 경우 설정해야하는 메소드 갯수가 다르다.
JPA의 경우
코드 전문은 아래에 존재하고, 중요한 부분을 짧막하게 설명하겠다.
기본 전제는 다음과 같다. 가장 기본으로 사용해야할 DB에 @Bean설정시 반드시 @Primary
설정을 잡아줘야한다.
잡지 않는 경우 같은 이름의 Bean이 여러개 잡히는걸로 인식해버렸다.
@EnableJpaRepositories(
basePackages = "xxx.xxxx.xxx.servicetask",
entityManagerFactoryRef = "adminEntityManager",
transactionManagerRef = "adminTransactionManager"
)
- JPA의 설정을 새로 다 잡아줘야되기 때문에 기본값으로 설정되는 것을 대체하기 위한 Entity를 담당 하는 매니저 팩토리와 Transaction매니저를 이 class에 설정해야한다. 그리고 실제로 이 JPA가 어디서 설정되는지 위치(
basePackages
)를 알려줘야한다.
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource adminDataSource(){
return DataSourceBuilder.create().build();
}
2. 이 Datasource를 설정하기 위한 것 driver주소, username password를 설정할 수 있다. SpringBoot의 경우 다음과 같이 ConfigurationProperties를 통해서 yaml파일에 설정되어있는 것을 보고 이름과 매핑시켜서 알아서 세팅해준다.
그리고 큰 차이를 만들어 내는 것은 @ConfigurationProperties(prefix = "spring.datasource") 인데 여기서 yaml파일을 수정 prefix를 변경하면 다른 DB와 관련된 설정을 할수 있다.
예를 들면 다음처럼, @ConfigurationProperties(prefix = "spring.jdbc-datasource")
yaml에 prefix만 다르게 해서 설정하자.
Spring 버젼에서는 그것을 일일히 코드화 시켜야했다. 다음과 같이 사용할 수 있었다.
@Primary
@Bean
public DataSource userDataSource() {
DriverManagerDataSource dataSource
= new DriverManagerDataSource();
dataSource.setDriverClassName(
env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("user.jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.pass"));
return dataSource;
}
극혐
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean adminEntityManager(){
LocalContainerEntityManagerFactoryBean em =new LocalContainerEntityManagerFactoryBean();
em.setDataSource(adminDataSource());
em.setPackagesToScan(newString[]{"xxxx.xx.xxx.servicetask"});
HibernateJpaVendorAdapter vendorAdapter =newHibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties =new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", property.getHbm2ddl().get("auto"));
properties.put("hibernate.format_sql",true);
properties.put("hibernate.show-sql",property.getShowSql());
properties.put("hibernate.dialect", property.getDialect());
properties.put("hibernate.storage_engine", property.getStorage_engine());
em.setJpaPropertyMap(properties);
return em;
}
@Bean
@Primary
public PlatformTransactionManager adminTransactionManager(){
JpaTransactionManager transactionManager =new JpaTransactionManager();
transactionManager.setEntityManagerFactory(adminEntityManager().getObject());
return transactionManager;
}
3. 트랜젝션과 엔티티를 설정해주는 매니저 팩토리를 이 class에서 설정한다.→ YAML properties적용 방식
그리고 hibernate의 기본 설정들을 적용하는 것 역시 EntityManager
에서 설정해야한다.
Properties 적용 방식에 대해서는 다음 포스팅을 참고하자 -> 곧 작성 예정
현재 나의 경우 YAML 파일을 강제하고 싶어서 다른 사람들이 쓴 코드와 약간의 차이점이 존재한다.
예를 들면 Environment env
과 같은 코드를 사용하지 않았는데, yaml의 경우 env.getProperty()를 이용할 수 없기때문에 사용하지 않았고, yaml을 강제하려면 @value를 통해서 코드를 구성해야했지만, 그렇게 하면 코드가 난잡해지고 길어지기 때문에 Property를 컴포넌트로 빼서 mapping하는 방식으로 작성했다.
✅ 만약 자신이 JPA를 통해서 2개의 DB를 사용해야하는 경우에는 Config.class를 한개 더 생성하고 이 Class에서는 @Primary
를 제거하고 생성하자
아래는 코드이다.
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
@Configuration
@RequiredArgsConstructor
@EnableJpaRepositories(
basePackages = "xxx.xx.xxx.servicetask",
entityManagerFactoryRef = "adminEntityManager",
transactionManagerRef = "adminTransactionManager"
)
public classPersistenceAdminConfig {
private finalJpaHibernateProperty property;
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource adminDataSource(){
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean adminEntityManager(){
LocalContainerEntityManagerFactoryBean em =new LocalContainerEntityManagerFactoryBean();
em.setDataSource(adminDataSource());
em.setPackagesToScan(newString[]{"xx.xxxx.xxxxx.servicetask"});
HibernateJpaVendorAdapter vendorAdapter =new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties =newHashMap<>();
properties.put("hibernate.hbm2ddl.auto", property.getHbm2ddl().get("auto"));
properties.put("hibernate.format_sql",true);
properties.put("hibernate.show-sql",property.getShowSql());
properties.put("hibernate.dialect", property.getDialect());
properties.put("hibernate.storage_engine", property.getStorage_engine());
em.setJpaPropertyMap(properties);
return em;
}
@Bean
@Primary
public PlatformTransactionManager adminTransactionManager(){
JpaTransactionManager transactionManager =newJpaTransactionManager();
transactionManager.setEntityManagerFactory(adminEntityManager().getObject());
return transactionManager;
}
}
JDBC의 경우
오히려 JDBC는 더 쉽다. 더 간략하게 처리가 가능하다.
JDBC는 결국 JdbcTemplate를 통해서 query가 날라가는 구조이기 때문에, JDBC template만 datasource와 일치화만 잘 잡아주면 되는 구조이다.
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
@Configuration
public class PersistenceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.jdbc-datasource")
public DataSource jdbcDatasource(){
return DataSourceBuilder.create().build();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(jdbcDatasource());
}
}
물론 여기서도 Transactionmanger와 같은 manger등록 역시 가능하다. 나는 Select문만 사용하므로 굳이 TransactionManager를 사용하지는 않았다.
결론
결국 2개의 DB를 사용하기 위해서는 이전에 간단하게 사용하는 Yaml에 datasource로는 사용하기 어렵고, 따로 Configuration 클래스를 2개 이상 잡아서 그곳에서 datasource와 그설정을 따로 잡아줘야한다.
귀찮더라도 할수 있는 방법은 존재하므로 위의 방법을 상기해서 사용하면 좋을 것 같다.
참고자료:
JPA DB를 따로 적용해야하는 경우
https://linkeverything.github.io/springboot/multiple-datasource/
https://2dongdong.tistory.com/33
JDBC 적용
https://knight76.tistory.com/entry/spring-boot-multidatasource-multi-db-간단하게-사용하기
'Spring > JPA' 카테고리의 다른 글
Hibernate 신기능 @SoftDelete 기능 (0) | 2024.04.10 |
---|---|
Spring boot 3.0.0 SQL Basic Binder 로깅 안되는 문제 해결기 (0) | 2022.12.02 |
Spring Data JPA에서 Query를 사용하는 방법 (1) | 2020.09.06 |