[Sprint 백엔드 초급 프로젝트 7일차] 뉴스 기사 view와 논리/물리 삭제 구현
뉴스 기사 view와 논리/물리 삭제 구현
같은 사용자의 반복 조회를 어떻게 볼까?
뉴스 기사 view 등록 로직에서 가장 먼저 기준으로 잡아야 할 것은 같은 사용자가 같은 기사를 여러 번 조회하더라도 조회 수는 1회만 반영되어야 한다. 실제 schema에서도 article_view_histories(뉴스 기사 조회 이력) table에 사용자 ID와 뉴스 기사 ID에 유니크 제약을 둬서 이를 보장하고 있다.
처음에는 기존 조회 이력이 있는지만 확인하고, 없으면 저장하고 있으면 무시하면 된다고 봤다. 하지만 이런 방식은 API 문서에 적혀있던 응답 구조와 맞지 않았다. 조회 이력이 없어서 새롭게 등록되든지, 기존 조회 이력이 있든지 간에 항상 동일한 응답을 반환해야 했다.
그래서 기존 조회 이력을 먼저 직접 조회하고, 없을 때만 새로 저장하는 방식으로 구현했다. 이렇게 하면 이미 조회한 사용자도 같은 방식의 성공 응답을 받을 수 있고, 새로 조회한 사용자도 동일한 형태의 응답을 받을 수 있다. 결론적으로 중복 요청을 실패로 볼 것이 아니라 최종 상태가 이미 반영되어 있다면 그대로 성공으로 응답하는 멱등적인 API로 보는 쪽이 더 효율적이라고 판단했다.
view 등록에서 save가 아닌 saveAndFlush를 사용한 이유
처음에는 save만 호출해도 곧바로 insert가 실행될 거라고 생각했다. 하지만 JPA는 변경 내용을 영속성 컨텍스트에 모아두었다가 flush나 commit 시점에 SQL을 보낼 수 있다. 이 경우 작성한 중복 예외가 save를 호출했을 시점이 아니라 트랜잭션 종류 시점이나 뒤의 다른 쿼리 실행 시점에 발생할 수 있다. 그러면 try-catch 안에서 중복 저장 충돌을 처리하려는 로직과 맞지 않는다.
반면 saveAndFlush를 사용하면 저장 즉시 flush를 수행하므로, unique 제약 조건 위반이 있다면 그 시점에 바로 예외가 발생할 수 있다.
논리 삭제와 물리 삭제를 구현하며 헷갈렸던 점
뉴스 기사 논리/물리 삭제를 처음에 단순하게 구현했다. 논리 삭제는 deleted_at만 채우면 끝이고, 물리 삭제는 말 그대로 지우면 끝이라고 생각했다.
기본은 논리 삭제이고 관련된 정보가 유지되어야 한다. 반면 물리 삭제는 관련 정보까지 모두 함께 삭제되어야 한다.
특히 entity에 @SQLDelete와 @SQLRestriction을 적용하면 repository.delete()가 실제로는 물리 삭제가 아니라 deleted_at을 업데이트하는 논리 삭제로 동작하게 된다. 그래서 삭제 로직은 아래처럼 구현했다.
- 일반 삭제는
@SQLDelete가 적용된 논리 삭제 사용 - DB에서 완전 제거는 별도의 물리 삭제 Repository 메서드 사용
@SQLRestriction이 있을 때 물리 삭제가 바로 안되는 이유
조회 시점에 @SQLRestriction("deleted_at IS NULL")가 자동으로 걸리기 때문에 이미 논리 삭제된 데이터는 일반 조회에는 보이지 않는다. 문제는 이 상태에서 삭제 대상 entity를 일반 조회로 가져오려고 하면 이미 필터링되어 보이지 않는다는 점이다.
물리 삭제를 하려면 보이지 않는 데이터를 어떻게 다룰 것인지 고민해야 했다. 이 부분은 JPQL DELETE도 @SQLRestriction 영향으로 논리 삭제된 데이터를 못 지우므로 native SQL을 이용한 별도 Repository 메서드를 두는 방식이 더 명확하다고 판단했다
팀 Notion 주소
[SB10-5팀] Sprint Spring 백엔드 중급 팀 프로젝트
Leave a comment