Just Do IT!

JPA 특정 엔티티 삭제 시 연관 관계 엔티티 삭제하기 본문

개발 공부/Spring

JPA 특정 엔티티 삭제 시 연관 관계 엔티티 삭제하기

MOON달 2024. 10. 29. 10:51
728x90
반응형

프로젝트를 진행하는데,

특정 엔티티를 삭제할 때 연관된 엔티티를 전부 삭제하도록 해야 하는데 제대로 되지 않았다.

 

이 부분을 팀원분이 해결해주셨고, 나는 그걸 참고해서 내가 맡은 문제집 쪽 삭제 로직을 수정하였다.

 

https://daydream-sy.tistory.com/358

 

JPA Cascade 알아보기

Cascade란? 부모 엔티티가 영속화될 때 자식 엔티티도 같이 영속화되고, 부모 엔티티가 삭제될 때 자식 엔티티도 삭제되는 등 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태

daydream-sy.tistory.com

 

여기서 정리했던 CascadeType.ALL과 orphanRemoval=true 옵션을 사용했고

다른 로직도 일부 수정하면서 테스트 해본 결과 한번에 삭제되는 걸 알 수 가 있었다.

 

 

 

 

연관관계 설명

문제집 쪽에는 여러 연관 관계가 있다.

 

DBeaver에서 확인한 연관 관계이다. 이걸 보면 하나를 삭제할 때 다른 연관 entity가 전부 삭제되어야 함을 알수 있다.

 

문제집을 삭제하는 경우

  • 해당 문재집에 포함된 문제 전부 삭제
  • 문제집 북마크 삭제
  • 문제집에 달린 댓글 삭제
  • 문제집에 달린 댓글 좋아요 삭제

가 되어야 한다. 아래 블로그 정리글에는 문제집과 문제 관계만 설명했다.

 

 

기존 entity 구성은 아래와 같았다.

public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false)
    private Long id;

    @Column(nullable = false)
    private String title;

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    @Builder.Default
    private Category category = Category.CATEGORY_ETC;

    @Column(nullable = false)
    @Builder.Default
    private boolean secret = false; // 비공개가 default

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @CreatedDate
    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
}

 

이게 문제집 entitiy였고

 

public class Question {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false)
    private Long id;

    @Column(nullable = false)
    private String question;

    @Column(nullable = false, name = "model_answer", length = 1000)
    private String modelAnswer;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "book_id")
    private Book book;

    @CreatedDate
    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    public void updateQuestion (String question, String modelAnswer) {
        this.question = question;
        this.modelAnswer = modelAnswer;
    }
}

 

이게 문제집 포함된 문제 entity였다.

 

@ManyToOne으로 설정했고, 이를 통해 해당 문제가 어떤 문제집에 포함된 것인지 관계를 나타내주었다.

이는 여러 문제가 하나의 책에 연결될 수 있음을 의미한다.

 

 

그런데 이렇게 entity를 설정하고 삭제하는 로직을 구현하였는데 설정 오류가 생겼다.

바로 문제집을 삭제했을 때, 문제집에 달린 댓글은 전혀 삭제되지 않는 것이다.

분명 연관관계가 되어있는데 왜 삭제가 안되는 걸까?

 

포스트맨으로 확인해보면 각각 댓글 삭제와, 문제집 삭제는 잘 진행되었다.

그런데 문제집이 삭제되면 문제집에 달린 댓글도 함께 삭제되어야 하는데 그부분이 고민이었다.

 

아래는 팀원분이 해결한 내용을 바탕으로 해결한 내용을 블로그로 정리한 것이다.

 

 

 

 

 

 

 

1. 부모 entity에 @OneToMany 어노테이션 추가

    @OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Question> questions;

    @OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Bookmark> bookmarks;

 

문제집 entity에 위 어노테이션을 추가해주었다.

column으로 설정하지 않았고 그냥 관계만 설정해준 것이다.

 

JPA에서 연관 관계를 설정할 때 여러 옵션을 추가할 수 있다.

https://daydream-sy.tistory.com/358

 

JPA Cascade 알아보기

Cascade란? 부모 엔티티가 영속화될 때 자식 엔티티도 같이 영속화되고, 부모 엔티티가 삭제될 때 자식 엔티티도 삭제되는 등 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태

daydream-sy.tistory.com

 

cascade 관련 설명은 위 글에 잘 설명해두었고,

@OneToMany와 @OneToOne에서 지원하는 orphanRemoval 옵션을 사용해주었다.

 

  • orphanRemoval = true
    • 부모 엔티티(문제집)이 사라지면서 자식 엔티티(문제)와의 참조가 끊어지면서 문제가 함께 삭제된다.
  • CascadeType.ALL
    • 원래 엔티티가 삭제될 때 연관된 엔티티를 전부 삭제하는 옵션

 

이 두가지 옵션을 통해,

문제집을 삭제하는 경우 전부 삭제되도록 한 것이다.

 

이는 다른 연관관계에서도 마찬가지로 적용해주었다.

 

2. 자식 entity의 repository에 삭제 메소드 추가

@Repository
public interface BookmarkRepository extends JpaRepository<Bookmark, Integer> {

    // 문제집 ID로 삭제
    void deleteByBook(Book book);
}

 

문제집 북마크 기능을 예시로 들었다.

북마크도 얼마든지 삭제 가능하지만, 문제집이 삭제되는 경우 함께 삭제되어야 하기 때문에

repository에 문제집 id로 삭제하는 메소드를 추가해주었다.

 

3. 상위 entity의 service 삭제 로직 수정

    // 문제집 삭제
    @Override
    @Transactional
    public void deleteBook(Long id, User user) {
        Book book = bookRepository.findById(id)
            .orElseThrow(() -> new BaseException(ErrorCode.BOOK_NOT_FOUND));

        // 문제집 소유자 확인
        if (!book.getUser().getId().equals(user.getId())) {
            throw new BaseException(ErrorCode.ACCESS_DENIED);
        }

        bookmarkRepository.deleteByBook(book);
        bookCommentRepository.deleteByBookId(book.getId());
        bookRepository.delete(book);
    }

 

문제집이 삭제 되는 경우 원래는 기존 delete 메소드만 존재했었다.

그런데 관계된 entity를 전부 삭제하기 위해서 로직을 추가해주었다.

 

위에 북마크 repository에 추가했던 문제집 id로 삭제하는 기능을 포함해, 댓글 삭제하는 기능까지 추가해주었다.

 

이렇게 추가하면, 문제집을 삭제할 때 문제집에 포함된 모든 entity도 함께 삭제되는 걸 확인할 수 있다.

 

 

 

 

 

 

 

 

 


나는 그저 참고해서 내가 맡은 부분을 수정했지만,

추후에 다른 프로젝트를 하거나 공부할 때 필요할 것 같아서 블로그에 정리해두었다.

JPA는 확실히 어려운것 같다.(ㅋㅋㅋㅋ)

728x90