← 프로젝트 회고 · Backend
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.

원인

  1. 두 설정 클래스의 @Bean 메서드 이름이 같았다. 빈 이름을 따로 주지 않으면 메서드 이름이 곧 빈 이름이다.
  2. 공통 모듈에 있던 설정 클래스가 컴포넌트 스캔 범위에 들어와 한 번 더 등록되기도 했다.
  3. 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);
}
}

주의할 점

  1. 주 DB 쪽 빈에만 @Primary를 붙인다. 이름을 안 적은 @Transactional은 기본 트랜잭션 매니저를 쓰기 때문에, 보조 DB 작업은 @Transactional("smsTransactionManager")처럼 명시해야 한다.
  2. DataSource를 직접 만들면 spring.datasource.* 자동 설정이 적용되지 않는다. 커넥션 풀 설정도 해당 prefix 아래에 따로 써야 한다.
  3. spring.main.allow-bean-definition-overriding=true로 덮는 건 피했다. 어느 빈이 이기는지가 로딩 순서에 달려 있어서 문제를 숨길 뿐이다.
Gunmo Lee
Spring Boot 다중 DataSource 설정에서 빈 이름이 충돌할 때 | Gunmo's Dev Life