@Override public List<Review> myReviews(){ returnnew ArrayList<>(); } }
그림으로
Service에서 Repository를 선언하여 사용하고, Repository는 CustomRepository를 상속받고, CustomRepositroy를 CustomRepositoryImpl가 구현체로 작성된 상태이다.
이때, ReviewFindService의 findMyReviews()를 호출하면, 아래와 같은 exception이 발생하고, 코드가 정상적으로 작동하지 않았다.
Caused by: java.lang.IllegalArgumentException: Failed to create query for method publicabstract java.util.List com.jgji.sokdak.domain.review.domain.ReviewCustomRepository.myReviews()! No property 'myReviews' found for type 'Review' at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:96) at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:119) at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:259) at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:93) at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:103) ... 134 more Caused by: org.springframework.data.mapping.PropertyReferenceException: No property 'myReviews' found for type 'Review' at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:91) at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:438) at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:414) at org.springframework.data.mapping.PropertyPath.lambda$from$0(PropertyPath.java:367) at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:349) at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:332) at org.springframework.data.repository.query.parser.Part.<init>(Part.java:81) at org.springframework.data.repository.query.parser.PartTree$OrPart.lambda$new$0(PartTree.java:250) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177) at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at org.springframework.data.repository.query.parser.PartTree$OrPart.<init>(PartTree.java:251) at org.springframework.data.repository.query.parser.PartTree$Predicate.lambda$new$0(PartTree.java:384) at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177) at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) at org.springframework.data.repository.query.parser.PartTree$Predicate.<init>(PartTree.java:385) at org.springframework.data.repository.query.parser.PartTree.<init>(PartTree.java:93) at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:89) ... 138 more
우선, 구현체인 ReviewCustomRepositoryImpl에 Break point 걸어보니, 아예 ReviewCustomRepositoryImpl의 myReviews()가 호출조차 되지 않는 것을 확인했다. 그 후 이것저것 코드도 수정해 보고 하였는데, ReviewCustomRepository를 따로 선언하여 사용하니 정상적으로 호출되었다.
public Review findById(long id){ returnthis.reviewRepository.findById(id) .orElseThrow(EntityNotFoundException::new); }
public List<Review> findMyReviews(){ returnthis.reviewCustomRepository.myReviews(); } }
기존에 계속 사용하던 구조이기에, CustomRepository를 선언하여 사용하는 방식으로는 문제 해결이 안 된다고 판단하여, 서론에서 말했던 것처럼 최근에 작업한 패키지 구조 수정 중 무언가 문제가 발생하였다고 판단되어 패키지 구조를 열어보니…
위 이미지처럼, Custom Repository interface와 구현체인 Custom Repository Impl 클래스가 서로 다른 패키지에 존재하고 있었고,
위처럼 같은 패키지 아래 존재 하도록 위치를 조정해주니 정상적으로 동작하는 것을 확인할 수 있었다. interface는 기본적으로 접근제어자가 public으로 선언되는데, 패키지 위치가 다른 것이 어째서 문제가 되는지 찾아보니.. Spring 공식 문서에서 아래와 같은 가이드를 찾을 수 있었다.
The repository infrastructure tries to autodetect custom implementation fragments by scanning for classes below the package in which it found a repository. These classes need to follow the naming convention of appending a postfix defaulting to Impl.
결론
결론적으로, Custom Repository를 작성할 때는 구현체 클래스에 postfix로 Impl가 붙게 만들어야 하고, interface와 같은 위치, 혹은 하위 패키지에 존재하도록 하여야 한다는 것을 알았다.