Conversation
| """ | ||
| select bi from BookItem bi | ||
| join Book b on b.id = bi.bookId | ||
| where b.deletedAt is null |
There was a problem hiding this comment.
[HIGH] 신규 search 쿼리에 BookItem 소프트 삭제 필터(bi.deletedAt is null) 누락
Problem: BookItem은 BaseEntity를 상속해 deletedAt/softDelete()를 갖고 있고, 이 저장소의 조회 쿼리 컨벤션은 항상 대상 엔티티의 deletedAt is null을 거는 것이다. 이 쿼리는 조인된 Book의 deletedAt만 필터링하고 BookItem 자신의 소프트 삭제 여부는 거르지 않는다.
Evidence: BookRepository의 모든 조회 메서드(findByIdAndDeletedAtIsNull, findAllByDeletedAtIsNull, searchActive)는 예외 없이 deletedAt is null을 포함한다. 동일한 갭이 PR #17에서 findByManagementNumberForUpdate에 대해 이미 [HIGH]로 지적된 적이 있고(당시 BookItem 삭제 기능이 아직 없어 보류됨), 지금 새로 추가되는 search 쿼리에서 그대로 반복되고 있다.
Fix direction: where 절에 bi.deletedAt is null 조건을 추가한다.
where bi.deletedAt is null
and b.deletedAt is null
and (:bookTitle is null or lower(b.title) like lower(concat('%', :bookTitle, '%')))
and (:managementNumber is null or lower(bi.managementNumber) like lower(concat('%', :managementNumber, '%')))There was a problem hiding this comment.
수정 완료 (bi.deletedAt is null을 추가)
BookItem.softDelete()를 호출하는 코드가 없어(현재 소장본 삭제 기능 미구현) 실질적인 동작 변화는 없음- 작업 중 별개로,
DeleteBookService가 도서 삭제 시 소장본을 cascade soft-delete 하지 않아 "도서는 삭제됐지만 소장본은 살아있는" 상황이 존재함을 확인함
→ 이건 현재 이슈에서 검색 쿼리에 필터를 추가하는 것 대신 원인(DeleteBookService) 자체를 고치는 게 맞다고 판단해 별도 이슈로 분리
(참고 리뷰 #17)
| @Query( | ||
| """ | ||
| select bi from BookItem bi | ||
| join Book b on b.id = bi.bookId |
There was a problem hiding this comment.
[MEDIUM] 모듈 경계를 넘는 JPQL 조인으로 book 모듈 엔티티 필드에 직접 의존
Problem: BookItemRepository(bookitem 모듈)가 Book(book 모듈)과 JPQL로 직접 조인하고 b.title, b.deletedAt 필드를 참조한다. 이는 이 저장소에서 유일한 모듈 간 JPQL 조인이며, Book 엔티티의 필드명이나 소프트 삭제 방식이 바뀌면 bookitem 모듈 코드가 함께 깨지고, bookitem 모듈만 단독으로 테스트하기도 어려워진다.
Evidence: 동일하게 Book과 BookItem 데이터가 모두 필요한 GetBookService는 조인 대신 두 리포지토리를 각각 호출해 서비스 계층에서 조합한다. BookItem의 bookId가 이미 ID 참조라 엔티티 레벨 규칙은 지키고 있지만, 쿼리 레벨에서 모듈 경계가 깨진다.
Fix direction: BookRepository에서 제목으로 매칭되는 bookId 목록을 먼저 조회한 뒤, BookItemRepository는 bookId 목록으로만 필터링하도록 분리하는 것을 고려한다. Book 엔티티의 필드를 직접 참조하는 조인 없이, bookitem 모듈 자체 필드만으로 쿼리를 구성하는 방식이다.
There was a problem hiding this comment.
수정 완료 (BookItemRepository에서 Book으로의 JPQL join을 제거)
모듈 경계 문제 외에 추후 Book 검색을 OpenSearch로 전환할 확장성도 고려
단일 JPQL join이었다면 검색 엔진 전환 시 쿼리를 통째로 다시 짜야 하지만 지금 구조는 "제목 매칭" 단계만 OpenSearch 호출로 교체하면 되고 "bookId로 소장본 조회" 단계는 그대로 재사용
연관 이슈
작업 사항
GET /bookitems)bookTitle) 부분 일치 검색 (Book과 조인, 삭제된 도서는 제외)managementNumber) 부분 일치 검색테스트
주의 사항 및 참고사항