서론

패키지 구조를 수정 후, 기존에 정상 작동하던 custom repository에서 에러가 발생하여, 그 원인과 해결 방법에 대해 정리한다.


본론

코드의 구조는 아래처럼 되어 있다.

Service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequiredArgsConstructor
@Service
public class ReviewFindService {

private final ReviewRepository reviewRepository;

public Review findById(long id) {
return this.reviewRepository.findById(id)
.orElseThrow(EntityNotFoundException::new);
}

public List<Review> findMyReviews() {
return this.reviewRepository.myReviews();
}
}

Repository

1
2
3
public interface ReviewRepository extends JpaRepository<Review, Long>, ReviewCustomRepository {

}

Custom Repository

1
2
3
4
public interface ReviewCustomRepository {

List<Review> myReviews();
}

Custom Repository Implementation

1
2
3
4
5
6
7
8
@Repository
public class ReviewCustomRepositoryImpl implements ReviewCustomRepository {

@Override
public List<Review> myReviews() {
return new ArrayList<>();
}
}

그림으로


Service에서 Repository를 선언하여 사용하고, Repository는 CustomRepository를 상속받고, CustomRepositroy를 CustomRepositoryImpl가 구현체로 작성된 상태이다. 이때, ReviewFindService의 findMyReviews()를 호출하면, 아래와 같은 exception이 발생하고, 코드가 정상적으로 작동하지 않았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract 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를 따로 선언하여 사용하니 정상적으로 호출되었다.

Service에서 CustomRepository를 직접 참조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RequiredArgsConstructor
@Service
public class ReviewFindService {

private final ReviewRepository reviewRepository;
private final ReviewCustomRepository reviewCustomRepository;

public Review findById(long id) {
return this.reviewRepository.findById(id)
.orElseThrow(EntityNotFoundException::new);
}

public List<Review> findMyReviews() {
return this.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와 같은 위치, 혹은 하위 패키지에 존재하도록 하여야 한다는 것을 알았다.


참고 사이트

댓글 공유

  • page 1 of 1

Junggu Ji

author.bio


author.job