~/tech-blog/posts/spring-no-unique-bean — zsh
지금 --명이 보고 있습니다오늘 --누적 --

테스트에서 만난 NoUniqueBeanDefinitionException

테스트 코드를 작성하면서 발생한 NoUniqueBeanDefinitionException에 대해 정리하려고 합니다.

상황 설명

테스트 코드를 작성하던 중 트랜잭션과 관련해서 아래와 같은 에러가 발생하는 것을 확인했습니다.

2022-10-14 17:43:26.877 [main] DEBUG o.s.t.c.t.TestContextTransactionUtils.logBeansException(215) - Caught exception while retrieving PlatformTransactionManager for test context [DefaultTestContext@2bea5ab4 testClass = UserRoleTaskTest, testInstance = com.example.batch.task.UserRoleTaskTest@166fa74d, testMethod = testUserBan@UserRoleTaskTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@3d8314f0 testClass = UserRoleTaskTest, locations = '{classpath*:context/applicationContext*.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextCustomizers = set[[empty]], contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]]
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available: expected single matching bean but found 4: transactionManagerSlave,transactionManagerRecallSlave,transactionManager,transactionManagerRecall

로그를 보면 PlatformTransactionManager 타입의 빈을 하나 기대했는데, transactionManagerSlave, transactionManagerRecallSlave, transactionManager, transactionManagerRecall 4개가 발견되었다고 알려 줍니다.

문제점

tx:annotation-driven 기반으로 여러 트랜잭션 매니저를 설정한 것이 원인이었습니다.

더 조사를 해 봐야겠지만, 현재 파악한 내용으로는 아래 설정처럼 트랜잭션 매니저 빈을 2개 이상 등록해 두었기 때문에, 테스트 코드에서 어떤 빈을 사용할지 선택하지 못해서 발생하는 것으로 보입니다. 이 부분은 좀 더 명확하게 파악해서 수정하겠습니다.

XML
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="dataSource" ref="datasource1" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="dataSource" ref="datasource2" />
</bean>

테스트 컨텍스트가 트랜잭션 매니저를 타입으로 찾을 때 어떤 일이 생기는지 그리면 아래와 같습니다.

트랜잭션 매니저 이름을 지정하지 않은 테스트가 PlatformTransactionManager 타입으로 빈을 조회하자 transactionManager, transactionManagerSlave, transactionManagerRecall, transactionManagerRecallSlave 4개가 발견되어 NoUniqueBeanDefinitionException이 발생하는 흐름눌러서 원본 크기로 보기

같은 타입의 빈이 2개 이상일 때 생기는 충돌과 해결 방법은 조회 빈이 2개 이상 충돌할 때에서 정리하였습니다. 여기서는 그 대상이 PlatformTransactionManager였습니다.

해결 방법

테스트 코드에서 어떤 트랜잭션 매니저를 사용할지 직접 지정해서, Spring이 사용할 트랜잭션 매니저를 명시적으로 알려 주는 방법으로 해결했습니다.

Java
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional("transactionManager")
@Slf4j
public class testCode {

@Transactional에 트랜잭션 매니저 이름을 적으면, 테스트 컨텍스트가 타입이 아니라 이름으로 빈을 찾기 때문에 하나를 바로 고를 수 있습니다.

@Transactional("transactionManager")처럼 이름을 지정하면 테스트 컨텍스트가 타입 대신 이름으로 조회해 4개 중 transactionManager 빈 하나를 바로 사용하는 흐름눌러서 원본 크기로 보기

오류를 발견하기 어려웠던 점

아래의 동작 때문에 트랜잭션이 잘 작동한다고 착각했습니다. 트랜잭션이 잘 작동한 것이 아니라, RuntimeException으로 인한 롤백이었던 것입니다. 오류가 발생했는데도 잘 작동하는 것처럼 보여서 원인을 찾는 데 많이 헤맸습니다.

  • RuntimeException 때문에 트랜잭션을 롤백할지 결정되며, 따로 지정한 롤백 규칙이 없어서 기본 규칙으로 롤백됩니다.
  • RuntimeException 혹은 Error의 경우 롤백하지만, checked exception은 롤백하지 않습니다.
If no custom rollback rules apply,
the transaction will roll back on {@link RuntimeException} and {@link Error}
but not on checked exceptions.

checked exception에도 롤백하도록 지정하는 rollbackFor는 [JAVA] @Transactional 이란? 트랜잭션 성질 (1)에서 정리하였습니다.

마무리

오늘은 트랜잭션 매니저 빈이 여러 개 등록된 환경에서 테스트 코드가 NoUniqueBeanDefinitionException을 남긴 원인과, @Transactional에 트랜잭션 매니저 이름을 지정해 해결한 방법에 대해 정리하였습니다.

참조 : 응? 이게 왜 롤백되는거지? | 우아한형제들 기술블로그, tx:annotation-driven기반 Mulitiple Transaction 설정

댓글

GitHub 계정으로 로그인하시면 댓글과 질문을 남기실 수 있습니다. 남겨 주신 글은 이 저장소의 Discussions에 쌓입니다.

● main 231 posts UTF-8 LF © 2026 코징 RSS · GitHub