티스토리 뷰
이전 포스팅에서 Spring Boot와 MyBatis를 사용해 다중 데이터소스를 설정하고 패키지로 구분하는 방법을 다뤘습니다. 이번에는 @Qualifier 를 이용하여 Mapper 경로를 공유하면서도 다중 데이터소스를 사용하는 방법을 알아보겠습니다.
이전 포스팅 보러가기: [SpringBoot] Mybatis 다중 Datasource 적용하기
1. @Qualifier 의존성 주입 순서
- 타입으로 빈 검색: @Autowired가 붙은 필드나 생성자의 타입과 일치하는 빈을 찾습니다.
- 1개일 경우: 해당 빈을 바로 사용합니다.
- 2개 이상일 경우: 추가 조건으로 좁혀갑니다.
- @Qualifier가 명시된 경우: @Qualifier("이름")에 지정된 이름과 일치하는 빈을 찾습니다.
- 일치하는 빈이 있으면: 그 빈을 사용합니다.
- 없으면: 다음 단계로 넘어가지 않고 바로 예외 발생(NoSuchBeanDefinitionException).
- @Qualifier가 없는 경우: 타입이 같은 빈이 2개 이상일 때, 변수명(필드명) 또는 파라미터명과 이름이 일치하는 빈을 찾습니다.
- 일치하는 빈이 있으면: 그 빈을 사용합니다.
- 없으면: 예외 발생
- 모두 실패 시: NoUniqueBeanDefinitionException 또는 NoSuchBeanDefinitionException이 발생하며 컨테이너가 종료됩니다.
2. 간단한 예제
- application.yml
spring:
datasource:
primary:
jdbc-url: jdbc:h2:mem:primaryDB
username: sa
password:
driver-class-name: org.h2.Driver
secondary:
jdbc-url: jdbc:h2:mem:secondaryDB
username: sa
password:
driver-class-name: org.h2.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true
- 해당 예제에서는 H2 인메모리 데이터베이스를 사용하지만, 실제 프로젝트에서는 원하는 DB로 대체하면 됩니다.
- PrimaryDataSourceConfig.java
@Configuration
public class PrimaryDataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")
);
return factoryBean.getObject();
}
@Primary
@Bean(name = "primarySqlSessionTemplate")
public SqlSessionTemplate primarySqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- SecondaryDataSourceConfig.java
@Configuration
public class SecondaryDataSourceConfig {
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")
);
return factoryBean.getObject();
}
@Bean(name = "secondarySqlSessionTemplate")
public SqlSessionTemplate secondarySqlSessionTemplate(@Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- Mapper 경로를 classpath:mapper/*.xml로 통일합니다.
- Mapper 인터페이스와 XML
@Mapper
public interface UserMapper {
User findById(Long id);
}
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<select id="findById" resultType="com.example.demo.model.User">
SELECT id, name FROM users WHERE id = #{id}
</select>
</mapper>
-
Mapper 인터페이스는 하나만 정의하고, 두 데이터소스에서 공유합니다.
- UserService.java
@Service
public class UserService {
private final UserMapper primaryUserMapper;
private final UserMapper secondaryUserMapper;
public UserService(
@Qualifier("primarySqlSessionTemplate") SqlSessionTemplate primarySqlSessionTemplate,
@Qualifier("secondarySqlSessionTemplate") SqlSessionTemplate secondarySqlSessionTemplate
) {
this.primaryUserMapper = primarySqlSessionTemplate.getMapper(UserMapper.class);
this.secondaryUserMapper = secondarySqlSessionTemplate.getMapper(UserMapper.class);
}
public User getUserFromPrimary(Long id) {
return primaryUserMapper.findById(id);
}
public User getUserFromSecondary(Long id) {
return secondaryUserMapper.findById(id);
}
}
- SqlSessionTemplate.getMapper(): 각 데이터소스에 맞는 SqlSessionTemplate에서 동일한 UserMapper 인터페이스를 가져옵니다.
- @Qualifier로 primarySqlSessionTemplate과 secondarySqlSessionTemplate을 구분해 주입받습니다.
- UserController.java
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/primary/users/{id}")
public User getPrimaryUser(@PathVariable Long id) {
return userService.getUserFromPrimary(id);
}
@GetMapping("/secondary/users/{id}")
public User getSecondaryUser(@PathVariable Long id) {
return userService.getUserFromSecondary(id);
}
}
마무리
Mapper 경로를 공유하면 코드가 간결해지고 유지보수가 쉬워지는 장점이 있습니다. 특히 동일한 쿼리를 여러 데이터소스에서 실행해야 하는 경우에 유리합니다. 반면, 데이터소스별로 쿼리가 크게 다르다면 경로를 분리하는 게 더 나을 수도 있으니. 프로젝트 요구사항에 따라 유연하게 선택하세요. 감사합니다.
최근에 올라온 글
- Total
- Today
- Yesterday