본문 바로가기
study/jpa

[spring boot / spring data JPA] Parameter 0 of constructor in ~ required a single bean, but 2 were found 에러

by eunoia_DB 2022. 9. 4.

 

 

spring boot에서 기존의 JPA를 사용했을 때 보다 spring data JPA 를 사용했을 때

어떠한 이점이 있는지 비교해보기 위해 실제로 구현해 보기로 했다. 

 

* Spring Data JPA는 JPA를 이용한 구현체를 더욱 추상화시켜 더 쉽고 간편하게 JPA를 이용할 수 있게 해준다.

  즉, JPA를 사용했을 때 보다 편리하며 JPA의 구현 클래스 작성 필요 없이 인터페이스 만으로 개발을 가능하게 한다.

 


 

spring data JPA를 사용을 위해 Inteface 작성 후

두 개의 Interface (JpaRepository, MemberRepository)를 상속받았다.

 

* JpaRepository는 내부에 구현체를 자동으로 생성시켜 주기 때문에 따로 구현체를 생성하지 않아도 된다.

* JpaRepository 인터페이스의 정해진 규칙대로 메소드를 입력하면, Spring이 알아서 해당 메소드 이름에 적합한

 쿼리를 날리는 구현체를 만들어서 Bean으로 등록해준다.

 

 

 

 

그 후 웹 서버와 데이터가 잘 연동되는지 테스트하기 위해 Application의 main() 을 실행시켰지만

localhost:8080 서버가 실행되지 않았고 다음과 같은 에러가 발생했다.

 

 

Description:

Parameter 0 of constructor in hello.hellospring.service.MemberService required
a single bean, but 2 were found: 

memoryMemberRepository: defined in file [C:\study\hellospring\out\production
\classes\hello\hellospring\repository\MemoryMemberRepository.class]

springDataJpaMemberRepository: defined in hello.hellospring.repository.Spring
DataJpaMemberRepository defined in @EnableJpaRepositories declared on 
JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration



Action:

Consider marking one of the beans as @Primary, updating the consumer to accept
multiple beans, or using @Qualifier to identify the bean that should be consumedr
to identify the bean that should be consumed

 

 

에러를 그대로 해석해 보면 Memberservice에서 하나의 bean 요구했는데 2가지의 Bean을 찾았기 때문이다.

즉, 상속받은 두 가지 인터페이스에서 2가지의 Bean이 존재하기 이를 처리하는 과정에서 에러가 발생한 것이다.

 

 

그래서 각각의 인터페이스를 확인해 보았다.

 

먼저 MemberRepository 인터페이스를 확인해 보니, 인터페이스를 구현한 구현 클래스 내에 @Repository 가 있었다.

* @Repository 내부에 @Component 가 있기 때문에 자동으로 bean 등록이 된다.

 

 

 

 

그다음으로 JpaRepository 인터페이스를 확인해 보니 @NoRepositoryBean 이라는 어노테이션이 있었다.

 


@NoRepositoryBean ??

사실 이 어노테이션이 어떤 동작을 하는지 그리고 bean 등록을 해주는지도 잘 몰랐기 때문에

@NoRepositoryBean에 대한 의미를 아는 것이 먼저가 되었다..

(extends를 하고 있는 다른 인터페이스들을 다 봤지만 모두 @NoRepositoryBean 어노테이션을 달고 있었다.)

 

@NoRepositoryBean의 의미

@NoRepositoryBean 은 실제 프록시 빈으로 등록하지 않게 해주는 어노테이션이라고 한다.

즉 extensds 하고 있는 PagingAndSortingRepository 와 같은 Repository 들을 계속해서

빈으로 등록하지 않기 위해 있는 것이다.

 

 

이 의미를 알고 나서 나는 아래와 같은 생각이 들었다.

" bean으로 등록해주지 않는데 그렇다면 어떻게 이 Repository가 bean으로 등록된 것이지?  "

 

그래서 나는 다시 한번 구글링을 통해 검색을 했다..(무한 구글링)  

 

 

그 해답은..

spring data JPA는 스프링이 만들어서 제공하는 컴포넌트 이기 때문에 @Repository를 생략해도 JPA 관련 예외를 모두

스프링 예외로 변환해서 제공해준다는 것이다. 따라서 @Repository를 통한 예외처리 기능이 이미 포함되어 있다고 한다.

 

또한 JPARepository를 상속받으면컴포넌트 스캔으로 동작하는 게 아니라, 스프링 데이터에서 해당 인터페이스를 구현한 클래스를 찾아서 사용한다고 한다. 실제로는 인터페이스를 구현한 클래스를 바로 사용하는 게 아니라, 스프링이 동적으로 임의의 구현 클래스를 만들고, 내가 구현한 클래스를 연결해준다는 것이다.

 

즉, @NoRepositoryBean 과 무관하게 JPARepository를 상속받으면 자동으로 bean으로 등록해준다!


이 모든 이유를 알고 나서 bean의 충돌을 없애려고 것 선택한 방법은

MemoryMemberRepository(MemberRepository의 구현 클래스) 내의 @Repository 를 없애는 것이다.

 

단 기본적으로 JpaRepository 내에서 MemoryMemberRepository에 구현되어 있는 로직들이 대부분 구현이
되어있었고 
이로 인해 기능들이 작동하는 데에는 에러가 발생하진 않을 것이라고 생각했다.

(실제로 테스트 실행 후 정상 작동을 확인했다.)

 

 

하지만 이는 단순히 나와 같은 경우일 때만 가능하고 좋은 방법은 아니라고 생각한다.

예를 들어 두 가지의 인터페이스를 모두 활용해야 할 경우도 있을 텐데 이 경우의 해결 방법은 스프링 부트가 제시해준다.

바로 두 가지 어노테이션 @Primary@Qualifier 를 이용하는 것이다. 이 어노테이션을 사용하면 두 가지의 bean

충돌이 일어날 때 우선순위를 정해주는 등의 방법으로 충돌을 막아준다고 한다.

 

이 두가지의 방법도 다음 기회에 직접 코드로 구현하며 포스터를 작성해볼 생각이다.

 

 

 

 

 

 

 

 


Reference

https://www.inflearn.com/questions/110045

https://velog.io/@max9106/wtc-learning11

 

 

 

 

 

 

 

 

'study > jpa' 카테고리의 다른 글

[spring boot / spring data JPA] 엔티티 Auditing 적용  (0) 2022.09.24
[spring boot / JPA] @Entity @Table 정리  (0) 2022.09.11

댓글