Fix super class의 필드 접근 못하는 이슈 해결#3
Conversation
| final Class<?> itemClass = item.getClass(); | ||
| if (itemClass.getSuperclass() != null) { | ||
| final Class<?> superclass = itemClass.getSuperclass(); | ||
| final Field[] superClassFields = superclass.getDeclaredFields(); | ||
| for (Field field : superClassFields) { | ||
| if (field.getName().equals(fieldName)) { | ||
| field.setAccessible(true); | ||
| superclass.getDeclaredField("id"); | ||
| return field.get(item); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| final Field field = itemClass.getDeclaredField("id"); | ||
| field.setAccessible(true); | ||
| return field.get(item); | ||
| } catch (NoSuchFieldException | IllegalAccessException e) { |
There was a problem hiding this comment.
| final Class<?> itemClass = item.getClass(); | |
| if (itemClass.getSuperclass() != null) { | |
| final Class<?> superclass = itemClass.getSuperclass(); | |
| final Field[] superClassFields = superclass.getDeclaredFields(); | |
| for (Field field : superClassFields) { | |
| if (field.getName().equals(fieldName)) { | |
| field.setAccessible(true); | |
| superclass.getDeclaredField("id"); | |
| return field.get(item); | |
| } | |
| } | |
| } | |
| final Field field = itemClass.getDeclaredField("id"); | |
| field.setAccessible(true); | |
| return field.get(item); | |
| } catch (NoSuchFieldException | IllegalAccessException e) { | |
| protected Object getFieldValue(T item) { | |
| try { | |
| Class<?> itemClass = item.getClass(); | |
| while (itemClass != null) { | |
| Field[] fields = itemClass.getDeclaredFields(); | |
| for (Field field : fields) { | |
| if (field.getName().equals(fieldName)) { | |
| field.setAccessible(true); | |
| return field.get(item); | |
| } | |
| } | |
| itemClass = itemClass.getSuperclass(); | |
| } | |
| throw new IllegalAccessException(fieldName); | |
| } catch (IllegalAccessException e) { | |
| log.error("Not Found or Not Access Field: " + fieldName, e); | |
| throw new IllegalArgumentException("Not Found or Not Access Field"); | |
| } | |
| } |
여러 부모를 상속할 수 있기 때문에 위와 같은 코드를 제안드립니다.
jojoldu
left a comment
There was a problem hiding this comment.
Vlad 강의 관점으로 보면, 이 PR은 superclass에 선언된 key field를 읽지 못하는 문제를 잡는 방향은 좋습니다. 다만 no-offset/keyset pagination에서는 쿼리의 정렬 기준 key와 다음 page cursor로 추출하는 key가 반드시 같아야 합니다. 현재 구현은 superclass 대응 과정에서 id가 하드코딩되어 있어서, id가 아닌 no-offset key를 사용하는 기존 지원 범위를 깨뜨릴 수 있습니다.
| for (Field field : superClassFields) { | ||
| if (field.getName().equals(fieldName)) { | ||
| field.setAccessible(true); | ||
| superclass.getDeclaredField("id"); |
There was a problem hiding this comment.
[P1] fieldName과 일치하는 필드를 이미 찾은 뒤에 superclass.getDeclaredField("id")를 호출하고 있어서, superclass의 no-offset 기준 필드가 id가 아닌 경우 여기서 다시 실패합니다. Vlad식 keyset pagination 관점에서는 cursor로 저장하는 값이 order by/where에 사용한 key와 정확히 같아야 하는데, 현재 구현은 superclass 지원을 id 전용으로 좁혀버립니다. 이 호출은 제거하고, class hierarchy 전체에서 fieldName을 찾는 방식으로 처리하는 게 맞아 보입니다.
| } | ||
| } | ||
|
|
||
| final Field field = itemClass.getDeclaredField("id"); |
There was a problem hiding this comment.
[P1] fallback에서도 fieldName 대신 "id"를 찾고 있습니다. 기존 API는 new QuerydslNoOffsetNumberOptions<>(manufacture.categoryNo, Expression.DESC)처럼 PK가 아닌 NumberPath도 지원하고 테스트도 있는데, 이 변경이 들어가면 하위 클래스 필드 기준 no-offset에서 항상 id를 cursor로 읽게 됩니다. itemClass.getDeclaredField(fieldName)로 유지하고, superclass 케이스도 동일하게 fieldName 기준으로 탐색해야 합니다. 추가로 superclass의 non-id 필드를 기준으로 읽는 테스트가 있으면 회귀를 바로 잡을 수 있습니다.
jojoldu
left a comment
There was a problem hiding this comment.
토비님 헥사고날 아키텍처 관점으로 보면, 이번 PR은 증상 자체는 잘 짚었지만 해결이 여전히 core policy가 엔티티 내부 구조를 직접 파고드는 방식에 머물러 있습니다. 이 라이브러리의 핵심 도메인을 no-offset paging policy로 보면, Querydsl path와 JPA entity field 접근은 바깥쪽 어댑터 세부사항에 가깝습니다. 최소 수정으로는 hierarchy 탐색을 fieldName 기준으로 일반화하고, 다음 단계로는 key extractor를 명시적으로 받는 쪽이 더 좋은 경계입니다.
| protected Object getFiledValue(T item) { | ||
| try { | ||
| Field field = item.getClass().getDeclaredField(fieldName); | ||
| final Class<?> itemClass = item.getClass(); |
There was a problem hiding this comment.
[P2] 토비님 강의식으로 보면 여기서 core no-offset 정책이 item.getClass()와 reflection으로 엔티티 구조를 직접 해석하는 순간, 도메인 정책과 JPA/Querydsl 모델 형태가 강하게 엮입니다. 이번 PR의 즉시 해결책은 class hierarchy를 fieldName으로 끝까지 탐색하는 것이지만, 더 좋은 방향은 QuerydslNoOffsetOptions가 Function<T, K> keyExtractor를 받아 cursor 값을 추출하게 하는 것입니다. 그러면 Querydsl path는 쿼리 생성용 어댑터 역할만 하고, 결과 객체에서 key를 읽는 규칙은 명시적인 port처럼 드러납니다.
증상
Error Log
자세히
해결
해당 클래스가 슈퍼 클래스를 상속 받으면 해당 필드를 슈퍼 클래스에서 찾고 없으면 기존 코드의 자신의 필드에서 찾게 변경