프로젝트를 하면서, 게시글이나 유저 정보를 delete해야 하는 경우가 있었다. delete 자체는 jpa를 이용하면 간단하게 구현했었고 테스트도 항상 통과했었는데, 문제가 생겼다.
다음은 게시글 삭제를 위한 boardService의 deleteBoard 메서드이다.
@Override
@Transactional
// @PreAuthorize("hasAuthority('ADMIN') or #board.author.username == authentication.principal.username") //hasAuthority를 사용해야 함.여기서 hasRole은 단지 문자열 비교로
public void deleteBoard(Long id, HttpServletRequest request) { //권한을 확인하기에, 권한 계층을 고려하지 않음.
Board board = boardRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("deleteBoard에서 유효하지 않은 게시글 id" + id));
User user = getUserFromRequest(request);
if(!user.getUsername().equals(board.getAuthor().getUsername()) && !request.isUserInRole("ROLE_ADMIN")){
throw new AccessDeniedException("권한이 없습니다.");
}
boardRepository.deleteById(id);
log.info("BoardService deleteBoard ==> username : " + user.getUsername());
log.info("authorization ADMIN : " + request.isUserInRole("ROLE_ADMIN"));
}
request를 통해 http 메시지 헤더 정보를 가져오면, 미리 만든 getUserFromRequest에서 토큰을 추출해서 유저 정보를 리턴한다. 게시글 id를 검색해서 db에 데이터가 없으면 에러를 출력한다.
관리자 권한, 또는 그 글의 작성자가 아니면 글은 지울 수 없다. 권한 체크 까지 끝나면, deleteById를 통해 삭제가 진행된다.
작성하는 건 매우 쉬운 과정이다. 하지만 문제는, 실행했을 때 유저와 게시글을 찾는 select 쿼리만 터미널에 출력되고, 정작 delete 쿼리는 출력되지 않는다는 것이었다. 당연히 실제 DB에도 삭제가 적용되지 않아 게시글 데이터가 덩그러니 남아있었다.
delete쿼리가 실행되지 않는 이유?
jpa를 더 깊게 공부했어야 스스로 알아낼 법한 문제였다. 영속화의 개념을 기억해내야 한다.
boardDelete의 코드를 자세히 보자.
@Override
@Transactional
// @PreAuthorize("hasAuthority('ADMIN') or #board.author.username == authentication.principal.username") //hasAuthority를 사용해야 함.여기서 hasRole은 단지 문자열 비교로
public void deleteBoard(Long id, HttpServletRequest request) { //권한을 확인하기에, 권한 계층을 고려하지 않음.
Board board = boardRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("deleteBoard에서 유효하지 않은 게시글 id" + id));
User user = getUserFromRequest(request);
if(!user.getUsername().equals(board.getAuthor().getUsername()) && !request.isUserInRole("ROLE_ADMIN")){
throw new AccessDeniedException("권한이 없습니다.");
}
boardRepository.deleteById(id);
log.info("BoardService deleteBoard ==> username : " + user.getUsername());
log.info("authorization ADMIN : " + request.isUserInRole("ROLE_ADMIN"));
}
1. 먼저 find를 통해 board 엔티티 하나를 찾아낸다. find하면 이후 영속성 컨텍스트에 추가된다.
2. board만 추가되는 것이 아니라, 즉시 로딩 전략을 채택했다면 연관관계에 연결된 엔티티들도 영속화된다. (board의 하위 엔티티)
3. getUserFromRequest에는 user를 find하는 쿼리가 있다. user 엔티티 하나가 영속화 된다.
4. 마찬가지로 즉시 로딩 전략이면 user와 연관된 엔티티들도 영속화 된다. (user의 하위 엔티티)
5. delete를 통해 board엔티티를 삭제한다. 영속화된 board를 삭제하게된다.
여기까진 문제없어 보인다. 하지만 문제는 같이 영속화된 연관 엔티티들이다.

board 엔티티에서 연관관계를 맺기위한 부분이다. comment, applicantboardRelation등등 연결된 엔티티들이 많다. cascade를 통해 board가 삭제되면, 연관된 엔티티들도 삭제하도록 해서 해결한 줄 알았다.
하지만 user가 영속화되면서 같이 영속화되는 엔티티 중에, board의 하위 엔티티와 겹치는 엔티티가 존재했었다.
즉 두 번 영속화되는데, 그래서 한 번 cascade를 통해 삭제되어도 그 하위엔티티가 영속성 컨텍스트에 남아있어, delete 가 반영되지 않은 것이다. delete가 반영되려면 영속성 컨텍스트에서 없어져야 하는데 delete를 해도 남아있어 그런 것이다.
이를 해결하는 쉬운방법은 delete 메서드 안에서 연관관계들을 다 지우는 방법도 있었지만, user 가 로딩될 때 연관된 엔티티들이 로딩되지 않는 지연 로딩을 사용하면 해결됬었다.

user의 연관간계 코드에서 fetch전략을 지웠다. OneToMany의 디폴트 fetch 전략은 lazy, 즉 지연 로딩으로, user가 영속화 되어도 하위 엔티티들은 프록시 상태로 남는다. (영속화 되지 않는다) 그러자 delete 쿼리가 성공적으로 실행되었다.
'Backend > spring boot' 카테고리의 다른 글
spring boot : entity <-> dto 변환 방법에 대한 고민( MapStruct 에 대해) (0) | 2024.10.09 |
---|---|
spring boot jpa : soft delete 구현 (delete 후 데이터 복원) (0) | 2023.09.06 |
spring boot에서 DTO를 엔티티로 변환할 때 연관관계에 있는 엔티티와 연결하기 (0) | 2023.08.15 |
외부에서 spring boot 서버에 접속 해보기(접속 안 될 때 이슈, sk 브로드밴드 포트포워딩) (0) | 2023.08.11 |
spring boot 사이드 프로젝트 : service 테스트 (0) | 2023.07.12 |