JpaRepository를 상속하는 Repository에서 Failed to create query for method public abstract
와 같은 예외를 만났습니다.
Failed to create query for method public abstract를 google에 검색해보면 대부분 Entity의 PK인 id 변수를 Id로 표기하거나, Repository에서 선언한 메서드에 카멜케이스를 지키지 않아서 발생한 문제들이 나옵니다.
하지만 저는 조금 다른 문제에 직면했습니다.
@Entity
@Getter
public class Notification {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "notification_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
private boolean inquired;
//...
protected Notification() {
}
@Builder
private Notification(NotificationType notificationType, Member member, Post post, Comment comment) {
this.notificationType = notificationType;
this.member = member;
this.post = post;
this.comment = comment;
this.inquired = false;
}
//...
}
위와 같이 Member와 연관관계를 가지는 Notification이라는 Entity가 있습니다.
해당 엔티티에 대해 JpaRepository를 상속하는 NotificationRepository에서 existsByMemberIdAndInquired라는 메서드를 선언해 사용하고 있었습니다.
public interface NotificationRepository extends JpaRepository<Notification, Long> {
boolean existsByMemberIdAndInquired(Long memberId, boolean inquired);
}
추가적인 기능을 구현하다가, 아래와 같이 Notification와 연관관계를 맺는 Member의 id를 조회할 일이 있었습니다.
notification.getMember().getId();
위의 코드는 객체는 조회 함수로 내부를 공개하면 안된다는 디미터 법칙
에 좋지 않다고 생각을 해서, 아래와 같이
Notification에 getMemberId()라는 메서드를 추가했습니다.
@Entity
@Getter
public class Notification {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "notification_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
private boolean inquired;
//...
protected Notification() {
}
@Builder
private Notification(NotificationType notificationType, Member member, Post post, Comment comment) {
this.notificationType = notificationType;
this.member = member;
this.post = post;
this.comment = comment;
this.inquired = false;
}
//...
** public Long getMemberId() { // 추가
return member.getId();
}**
}
그 후, NotificationRepository를 사용하는 테스트를 실행했는데, 아래와 같은 예외
가 발생했습니다.
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract boolean
com.wooteco.sokdak.notification.repository.NotificationRepository.existsByMemberIdAndInquired(java.lang.Long,boolean)!
Unable to locate Attribute with the the given name [memberId] on this ManagedType [com.wooteco.sokdak.notification.domain.Notification]
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)
... 164 more
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute
with the the given name [memberId] on this ManagedType [com.wooteco.sokdak.notification.domain.Notification]
at org.hibernate.metamodel.model.domain.internal.AbstractManagedType.checkNotNull(AbstractManagedType.java:148)
at org.hibernate.metamodel.model.domain.internal.AbstractManagedType.getAttribute(AbstractManagedType.java:119)
at org.hibernate.metamodel.model.domain.internal.AbstractManagedType.getAttribute(AbstractManagedType.java:44)
at org.springframework.data.jpa.repository.query.QueryUtils.requiresOuterJoin(QueryUtils.java:814)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:755)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:734)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:730)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.getTypedPath(JpaQueryCreator.java:394)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.build(JpaQueryCreator.java:317)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.toPredicate(JpaQueryCreator.java:217)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:125)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:60)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:120)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:96)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:82)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.<init>(PartTreeJpaQuery.java:217)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$CountQueryPreparer.<init>(PartTreeJpaQuery.java:348)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:91)
... 168 more
existsByMemberIdAndInquired라는 메서드에 대한 Query를 만들 수 없다
는 예외입니다.
JpaRepository에서 메서드 이름과 파라미터를 통해 Query
를 만들어줄 때, 내부적으로 getMemberId()라는 메서드를 사용
하고, 이를 제가 재정의해서 발생한 문제
라고 추측됩니다.
그래서 JpaRepository가 메서드 이름과 파라미터를 통해서 Query를 생성할 때, 어떤 내부 흐름을 가지고 어떤 메서드들을 사용하는지 알아보려고 했습니다. 하지만, 김영한님의 책에 나와있지 않고, 구글링을 해보아도 정보를 찾을 수 없었습니다.
해당 부분은 조금 더 알아보고 추후에 공유하도록 하겠습니다!
혹시 제가 해결하지 못한 궁금증에 대해 아시는 분이 계시다면 댓글 달아주시면 감사하겠습니다!
끗