RETROSPECTIVE · 회고
Spring Boot 다중 DataSource 설정에서 빈 이름이 충돌할 때
Backend
한 서비스에서 주 DB와 문자 발송용 DB를 둘 다 JPA로 붙여야 했다. 설정 클래스를 하나 더 만들었더니 기동 단계에서 이런 에러가 났다.
The bean 'entityManagerFactory', defined in class path resource [.../SmsDbConfig.class],
could not be registered. A bean with that name has already been defined ...
and overriding is disabled.
원인
- 두 설정 클래스의
@Bean메서드 이름이 같았다. 빈 이름을 따로 주지 않으면 메서드 이름이 곧 빈 이름이다. - 공통 모듈에 있던 설정 클래스가 컴포넌트 스캔 범위에 들어와 한 번 더 등록되기도 했다.
- Spring Boot 2.1부터는 빈 덮어쓰기가 기본으로 막혀 있어서 바로 실패한다. 덕분에 조용히 잘못된 빈이 쓰이는 일은 없다.
설정 패턴
DataSource마다 DataSource, EntityManagerFactory, TransactionManager 세 개를 접두사 붙은 이름으로 만들고, 리포지토리 패키지를 분리해 연결한다.
@Configuration
@EnableJpaRepositories(
basePackages = "com.example.sms.repository",
entityManagerFactoryRef = "smsEntityManagerFactory",
transactionManagerRef = "smsTransactionManager")
public class SmsDbConfig {
@Bean
@ConfigurationProperties("app.datasource.sms")
public DataSource smsDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public LocalContainerEntityManagerFactoryBean smsEntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder.dataSource(smsDataSource())
.packages("com.example.sms.entity")
.persistenceUnit("sms")
.build();
}
@Bean
public PlatformTransactionManager smsTransactionManager(
@Qualifier("smsEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
주의할 점
- 주 DB 쪽 빈에만
@Primary를 붙인다. 이름을 안 적은@Transactional은 기본 트랜잭션 매니저를 쓰기 때문에, 보조 DB 작업은@Transactional("smsTransactionManager")처럼 명시해야 한다. - DataSource를 직접 만들면
spring.datasource.*자동 설정이 적용되지 않는다. 커넥션 풀 설정도 해당 prefix 아래에 따로 써야 한다. spring.main.allow-bean-definition-overriding=true로 덮는 건 피했다. 어느 빈이 이기는지가 로딩 순서에 달려 있어서 문제를 숨길 뿐이다.