일단 씻고 나가자
[Spring] (java.lang.IllegalArgumentException) Failed to create query for method 메서드 명 ! Unable to locate Attribute with the the given name [변수 명] on this ManagedType [클래스 명] → JPA 내부 동작 과정 query creation 본문
[Spring] (java.lang.IllegalArgumentException) Failed to create query for method 메서드 명 ! Unable to locate Attribute with the the given name [변수 명] on this ManagedType [클래스 명] → JPA 내부 동작 과정 query creation
일단 씻고 나가자 2023. 12. 12. 21:23* 핵심에 집중하기 위해 클래스, 메서드 명이나 로직 등 에러와 관련 없는 부분은 italic 과 연한 색 처리 및 모자이크 처리
문제 상황 (에러 본문)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'bean 이름' defined in file [클래스 경로]: Unsatisfied dependency expressed through constructor parameter 2;
nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'bean 이름' defined in 클래스 경로 defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Invocation of init method failed;
nested exception is org.springframework.data.repository.query.QueryCreationException:
Could not create query for 메서드 정보;
Reason: Failed to create query for method 메서드 정보!
Unable to locate Attribute with the the given name [필드 이름] on this ManagedType [클래스 이름];
nested exception is java.lang.IllegalArgumentException: Failed to create query for method 메서드 정보!
Unable to locate Attribute with the the given name [필드 이름] on this ManagedType [클래스 이름]
해결책 (선요약)
일반적으로 JPA에서 메서드를 규칙에 따라 만들 때, 엔티티 클래스 내부에 없는 필드의 명칭으로 선언한다거나 했을 시 발생한다.
나의 경우 디미터의 법칙에 맞게끔 코드를 리팩토링 하던 중, 엔티티 클래스 내부에서 get필드명() 메서드를 만들자 JPA 동작 과정 중 하나인 query creation 변환 과정에서의 메서드와 충돌하여 발생한 문제로,
get필드명() 메서드의 이름을 get필드명ByValidate() 으로 임시 수정하니 정상 작동하였다.
사건 개요
당시 나의 경우 Reservation 이라는 엔티티 안에 Member 라는 엔티티를 연관 관계로 가지고 있었다.
그리고 멘토링을 통해 '디미터의 법칙 (Law of Demeter)'을 적용하기 위하여, Reservation 엔티티 내부에 연관 관계로 얽혀 있는 member의 id를 반환하는 메서드를 다음과 같이 작성했다. (디미터의 법칙을 이 글에서 설명하진 않겠다)
그러자 Reservation 을 저장하는 JPA ReservationRepository 쪽에서 에러가 발생했다. (서버 실행 안 됨)
에러 로그는 하단의 메서드에서, BaseEntitiy 의 memberId 를 찾지 못하겠다는 내용이었다.
에러 로그에서 알 수 있듯, 에러의 핵심은 해당 메서드의 memberId 와 BaseEntity 였는데,
BaseEntitiy는 Reservation 엔티티가 상속 받고 있어서 스프링이 Reservation에서 memberId를 뒤져보다가 없으니 상속 관계인 BaseEntity까지 찾아갔고, 거기서도 없으니 로그를 BaseEntity로 낸 것으로 보였다. (실제로 Reservation 에서 extends BaseEntity만 지우고 실행해보니 로그에 BaseEntity 대신 Reservation 으로 뜬 것을 확인했다)
에러를 해결하기 위해 정말 많은 걸 수정해보고 바꿔가며 테스트 해보았다.
처음엔 findTop.. 으로 시작하는 메서드였는데, findTop1, findFirst, findFirst1 등등으로 고쳐보았고,
(근데 신기하게 First1/Top1 으로 뒤에 숫자를 붙이니까 memberId 에러가 storeId로 변했었다)
메서드에 오타는 없는지, 내가 findTop/First 메서드의 반환 타입을 잘못 시켰는지, Reservation 엔티티 내에 오타나 문제는 없는지 등등 하나씩 건드려보고 실행시키고 별 짓을 다 했는데..
생각해보니 디미터의 법칙을 적용하기 위한 메서드를 작성하기 전에는 서버가 잘 실행되던 것을 떠올렸고,
그래서 getMemberId() 메서드를 getMemberIdXXX() 형태로 바꾸어주니 정상 작동 되었다. (논리적인 로직도 정상 작동)
왜 해당 에러가 발생했을지 고민을 많이 해봤는데, 메서드 명칭에 문제가 있음이 확실하니 연관 관계로 얽혀 있던 컬럼 명인 member_id 가 카멜 케이스로 바뀌면서 에러를 냈을 것이란 추론이 강하게 들었다.
그래서 hibernate가 쿼리로 변환할 때 문제가 생긴 것 같아 이것저것 찾아보았으나.. 아직 초보인 나에게는 찾아보는 것도 어려운 일이었다.
그래서 멘토님께 여쭤보니 JPA Repository에서 query creation 변환 과정을 진행할 때 메서드 충돌이 일어난 듯 싶다고 말씀해주셨고, spring data JPA 하위 구현 동작 과정 중 필드명 + id 조합의 getter로 내부적으로 관리하는 것 같다고 답변해주셨다. 동작 과정이 담긴 코드도 첨부해주셨으나 역시 잘 이해하지는 못했다.. (나중에 성장하면 그때 꼭 다시 읽어보자)
( spring data 동작 과정 코드 github > 링크 < )