Skip to content

feat(#22): 소장본 검색 조회 API 추가 - #27

Merged
fnzl54 merged 1 commit into
mainfrom
feat/#22
Aug 16, 2026
Merged

feat(#22): 소장본 검색 조회 API 추가#27
fnzl54 merged 1 commit into
mainfrom
feat/#22

Conversation

@fnzl54

@fnzl54 fnzl54 commented Aug 16, 2026

Copy link
Copy Markdown
Member

연관 이슈

작업 사항

  • 소장본 검색·목록 조회(GET /bookitems)
    • 도서명(bookTitle) 부분 일치 검색 (Book과 조인, 삭제된 도서는 제외)
    • 관리번호(managementNumber) 부분 일치 검색

테스트

  • 로컬 서버 테스트 완료

주의 사항 및 참고사항

  • BookItem에는 도서명 필드가 없어 Book과 JPQL join으로 필터링 (관련 인덱스는 추후 성능 튜닝 시 검토 예정)

@fnzl54 fnzl54 self-assigned this Aug 16, 2026
"""
select bi from BookItem bi
join Book b on b.id = bi.bookId
where b.deletedAt is null

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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, '%')))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정 완료 (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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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 모듈 자체 필드만으로 쿼리를 구성하는 방식이다.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정 완료 (BookItemRepository에서 Book으로의 JPQL join을 제거)

모듈 경계 문제 외에 추후 Book 검색을 OpenSearch로 전환할 확장성도 고려
단일 JPQL join이었다면 검색 엔진 전환 시 쿼리를 통째로 다시 짜야 하지만 지금 구조는 "제목 매칭" 단계만 OpenSearch 호출로 교체하면 되고 "bookId로 소장본 조회" 단계는 그대로 재사용

@fnzl54
fnzl54 merged commit 8cb3d53 into main Aug 16, 2026
@fnzl54
fnzl54 deleted the feat/#22 branch August 20, 2026 04:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant