[Spring Boot] 다중 데이터베이스 사용하기
들어가며
하나의 어플리케이션에서 다중 DB에 접근할 수 있는 설정에 대해 알아보려고 한다.
대략적인 순서로는
- application.yml 설정파일에 db 연결정보 작성
- Config 파일 생성
- service, dao, mapper 파일 생성
- 테스트
위와 같으며, 패키지 구조도 함께 살펴보겠다.
0. 프로젝트 구조
프로젝트 구조는 다음과 같다. 중요하게 봐야할 부분은 Mapper 인터페이스 파일과 쿼리를 작성하는 xml 파일을 DB별로 나눠서 사용할 수 있게끔 나누었다.
1. application.yml 설정파일에 db 연결정보 작성
연결하려는 DB의 접속정보들을 작성한다.
2. Config 파일 생성
Mybatis를 사용하기 위해 HikariCP의 DataSource를 설정하였다.
HikariCP에 대한 설명은 해당 글을 참고하면 된다.
링크 : https://jangjjolkit.tistory.com/40
다중 DB를 사용하려면 DB마다 해당 작업을 진행해야 한다. 그런데 Spring의 Bean은 싱글톤을 사용하여 관리하기 때문에 여러 DataSource 관련 Bean을 아무런 설정 없이 사용하면 충돌이 난다.
그렇기 때문에 특정 DB의 config 파일에 @Primary 어노테이션을 붙여 특정 빈을 우선적으로 주입하게 하고, 그 외에 DB config 파일에는 @Qualifier 어노테이션을 붙여 식별자를 지칭해줘야 한다.
의존성 주입 시 @Primary, @Qualifier에 대한 설명은 해당 글을 참고하면 된다.
링크 : https://jangjjolkit.tistory.com/37
@Primary를 사용한 설정파일
@Configuration
@MapperScan(value = "com.jdh.practice.model.dao.test_a")
public class ADataSourceConfig {
private final String A_DATA_SOURCE = "ADataSource";
// A database DataSource
@Primary
@Bean(A_DATA_SOURCE)
@ConfigurationProperties(prefix = "spring.test-a.datasource.hikari")
public DataSource ADataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}
// SqlSessionTemplate 에서 사용할 SqlSession 을 생성하는 Factory
@Primary
@Bean
public SqlSessionFactory ASqlSessionFactory(DataSource dataSource) throws Exception {
/*
* MyBatis 는 JdbcTemplate 대신 Connection 객체를 통한 질의를 위해서 SqlSession 을 사용한다.
* 내부적으로 SqlSessionTemplate 가 SqlSession 을 구현한다.
* Thread-Safe 하고 여러 개의 Mapper 에서 공유할 수 있다.
*/
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// MyBatis Mapper Source
// MyBatis 의 SqlSession 에서 불러올 쿼리 정보
Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:mappers/a/*Mapper.xml");
bean.setMapperLocations(res);
// MyBatis Config Setting
// MyBatis 설정 파일
Resource myBatisConfig = new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml");
bean.setConfigLocation(myBatisConfig);
return bean.getObject();
}
// DataSource 에서 Transaction 관리를 위한 Manager 클래스 등록
@Primary
@Bean
public DataSourceTransactionManager ATransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
@Qualifier를 사용한 설정파일
@Configuration
@MapperScan(value = "com.jdh.practice.model.dao.test_b", sqlSessionFactoryRef = "BSqlSessionFactory")
public class BDataSourceConfig {
private final String B_DATA_SOURCE = "BDataSource";
// A database DataSource
@Bean(B_DATA_SOURCE)
@ConfigurationProperties(prefix = "spring.test-b.datasource.hikari")
public DataSource BDataSource() {
return DataSourceBuilder.create()
.type(HikariDataSource.class)
.build();
}
// SqlSessionTemplate 에서 사용할 SqlSession 을 생성하는 Factory
@Bean
public SqlSessionFactory BSqlSessionFactory(@Qualifier(B_DATA_SOURCE) DataSource dataSource) throws Exception {
/*
* MyBatis 는 JdbcTemplate 대신 Connection 객체를 통한 질의를 위해서 SqlSession 을 사용한다.
* 내부적으로 SqlSessionTemplate 가 SqlSession 을 구현한다.
* Thread-Safe 하고 여러 개의 Mapper 에서 공유할 수 있다.
*/
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// MyBatis Mapper Source
// MyBatis 의 SqlSession 에서 불러올 쿼리 정보
Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath:mappers/b/*Mapper.xml");
bean.setMapperLocations(res);
// MyBatis Config Setting
// MyBatis 설정 파일
Resource myBatisConfig = new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml");
bean.setConfigLocation(myBatisConfig);
return bean.getObject();
}
// DataSource 에서 Transaction 관리를 위한 Manager 클래스 등록
@Bean
public DataSourceTransactionManager BTransactionManager(@Qualifier(B_DATA_SOURCE) DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
- @Configuration 어노테이션을 사용하여 Spring Framework가 기동 시 설정파일 등록
- @MapperScan 어노테이션을 사용하여 Mapper 인터페이스 경로를 지정하고, sqlSessionFactoryRef로 어떤 sqlSessionFactory를 사용할지 지정
- 특정 DB config 파일에 @Primary 어노테이션을 붙여 우선적으로 등록
- DataSource 메소드 작성
- application.yml의 어떤 DataSoruce 설정 정보를 사용하는지 설정 (@ConfigurationProperties)
- SqlSessionFactory 메소드 작성
- @Primary 어노테이션을 사용하지 않는 config 파일에서 매개변수로 받는 DataSource에 @Qualifier를 사용하여 임의의 DataSource를 지정하여 충돌 방지
- TransactionManager 메소드 작성 (선택)
위와 같이 설정한 후에 Service 단에서 각각의 Mapper를 사용해서 DB에 접근할 수 있다.
3. service, dao, mapper 파일 생성
ATestMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jdh.practice.model.dao.test_a.ATestMapper">
<select id="selectTest" resultType="String">
select concat("A_", test_nm) from test_table;
</select>
</mapper>
ATestMapper.java
@Mapper
public interface ATestMapper {
String selectTest() throws Exception;
}
ATestService.java
@RequiredArgsConstructor
@Service
public class ATestService {
private final ATestMapper aTestMapper;
public String getTest() throws Exception {
return aTestMapper.selectTest();
}
}
BTestMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jdh.practice.model.dao.test_b.BTestMapper">
<select id="selectTest" resultType="String">
select concat("B_", test_nm) from test_table;
</select>
</mapper>
BTestMapper.java
@Mapper
public interface BTestMapper {
String selectTest() throws Exception;
}
BTestService.java
@RequiredArgsConstructor
@Service
public class BTestService {
private final BTestMapper bTestMapper;
public String getTest() throws Exception {
return bTestMapper.selectTest();
}
}
A DB에서 조회할 때는 조회 데이터 앞에 "A_" 를 붙여 조회하게 했고, B DB에서 조회할 때는 조회 데이터 앞에 "B_" 를 붙여 조회하게 했다.
4. 테스트
Test
@SpringBootTest
public class ServiceTest {
Logger log = (Logger) LoggerFactory.getLogger(this.getClass());
@Autowired
ATestService aTestService;
@Autowired
BTestService bTestService;
@Test
public void test() throws Exception {
log.info("mapper a :: " + aTestService.getTest());
log.info("mapper b :: " + bTestService.getTest());
}
}
테스트 코드를 작성하여 각각의 DB에서 데이터를 정확하게 조회하는지 확인하였다.
테스트 결과 A, B 각각의 DB에서 데이터를 조회하는 것을 확인하였다.
정리하며
관련 소스 코드는 깃허브를 참고하면 된다.
링크 : https://github.com/JangDaeHyeok/spring_boot_multiple_db_conn_practice