public interface MembershipQuery {
Map<Long, LeaderDto> findLeaderByStudies(List<Long> studyIds);
LeaderDto findLeaderByStudyId(Long studyId);
List<StudyMemberDto> findMembers(Long studyId, StudyUserFilterDto filter);
}
query용 interface를 구현하고 아래와 같은 문제가 발생했다.
...
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'membershipRepository' defined in co.kr.cocomu.study.repository.MembershipRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List co.kr.cocomu.study.repository.query.MembershipQuery.findMembers(java.lang.Long,co.kr.cocomu.study.dto.request.StudyUserFilterDto); Reason: Failed to create query for method public abstract java.util.List co.kr.cocomu.study.repository.query.MembershipQuery.findMembers(java.lang.Long,co.kr.cocomu.study.dto.request.StudyUserFilterDto); No property 'findMembers' found for type 'Membership'
Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.List co.kr.cocomu.study.repository.query.MembershipQuery.findMembers(java.lang.Long,co.kr.cocomu.study.dto.request.StudyUserFilterDto); Reason: Failed to create query for method public abstract java.util.List co.kr.cocomu.study.repository.query.MembershipQuery.findMembers(java.lang.Long,co.kr.cocomu.study.dto.request.StudyUserFilterDto); No property 'findMembers' found for type 'Membership'
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List co.kr.cocomu.study.repository.query.MembershipQuery.findMembers(java.lang.Long,co.kr.cocomu.study.dto.request.StudyUserFilterDto); No property 'findMembers' found for type 'Membership'
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property 'findMembers' found for type 'Membership'
구현체를 만들었음에도 findMembers를 찾을 수 없다고 한다.
원인은 Spring Data JPA의 DI 전략 때문이다.
Spring Data JPA는 기본적으로 @EnableJpaRepositories 어노테이션으로 base package에서 JpaRepository를 사용하고 있는지 확인한다.
public interface MembershipJpaRepository extends JpaRepository<Membership, Long> {
Spring Data JPA는 Repository 인터페이스가 JpaRepository를 상속하고 있을 경우, 자동으로 SimpleJpaRepository를 기반으로 한 구현체를 생성하고, 여기에 우리가 작성한 커스텀 구현체(MembershipQueryImpl)를 조합한 프록시 객체를 생성해 빈으로 등록한다.
final String implClassName = repositoryInterface.getName() + "Impl";`
하지만, 지금 내가 발생한 문제는 IntelliJ의 Rename 옵션 때문이다. 관련 클래스를 수정할 때, 비슷한 네이밍을 다 찾아서 Rename하는 특성이 있다.
public class MembershipImpl implements MembershipQuery {
...
}
기존 StudyUser 엔티티를 좀 더 명확한 네이밍으로 Membership으로 수정했는데 IntelliJ의 전략 때문에 구현체 이름이 매핑이 안된 것이다. 이 때, Query는 JpaRepository와 함께 사용되고 있었는데 SpringDataJpa가 연관된 interface인 query의 구현체도 찾는 과정에서 MembershipQueryImpl을 찾지 못한 것이다.
-> 실제로 MembershipImpl이라고 작성되었기 때문
그래서 spring data jpa는 membershipQuery의 method 구현체를 찾을 수 없어서 위와 같은 에러를 발생시킨 것이다.
정리
해결하려면 MembershipImpl -> MembershipQueryImpl로 수정하면 됨
MembershipRepository
├── JpaRepository (기본 구현: SimpleJpaRepository)
└── MembershipQuery (내가 정의한 QueryDSL 기반 메서드)
└── MembershipQueryImpl
→ 최종적으로 하나의 프록시 객체로 등록
JPA Custom Impl Docs - https://docs.spring.io/spring-data/jpa/reference/#repositories.custom-implementations