본문 바로가기
스터디일지/PROJECT

[Java Spring] CascadeType.REMOVE와 orphanRemoval = true

by 똥쟁이핑크 2023. 9. 9.

Level.3 과제를 하면서 게시글과 댓글을 구현하고 삭제하는 과정에서 생긴 궁금증이다.

게시글을 삭제할때 해당 게시글에 달린 댓글 까지 같이 삭제하는 과정에서 

CascadeType.REMOVE와 orphanRemoval = true의 차이점이 뭘까라는 궁금증이 생겼다.

그래서 기술매니저님께 질문했다.  자세하게 답변해주셨고 그걸 토대로 자료도 첨부해서 정리해 보았다.

 

아래는 코드를 실행한 결과다.

// 댓글 코드
// 게시글 하나에 댓글이 여러개 생성할 수 있다.
// 게시글 : 댓글 = 1 : N -> 양방향 관계다

@Getter
@Entity
@NoArgsConstructor
public class Comment extends Timestamped {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String content;

    @Column(nullable = false)
    private String username;

    @ManyToOne(fetch = FetchType.LAZY)
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "post_id") // 추가
    private Post posts;
// 게시글 코드
// 게시글 : 댓글 = One to Many

@Entity
@Getter
@Setter
@Table(name = "post")
@NoArgsConstructor
public class Post extends Timestamped {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@Column(name = "title", nullable = false)
	private String title;

	@Column(name = "username", nullable = false)
	private String username;

	@Column(name = "contents", nullable = false)
	private String content;


	@OneToMany(mappedBy = "posts", orphanRemoval = true)
	private List<Comment> commentList = new ArrayList<>();
[
    {
        "id": 6,
        "title": "2번",
        "content": "오늘도 마무리",
        "username": "soo12345",
        "createdAt": "2023-09-09T20:46:45.601216",
        "modifiedAt": "2023-09-09T20:46:45.601216",
        "commentList": [
            {
                "id": 7,
                "content": "돌고돈다",
                "username": "soo12345",
                "createdAt": "2023-09-09T20:48:26.101369",
                "modifiedAt": "2023-09-09T20:48:26.101369"
            },
            {
                "id": 8,
                "content": "내일도 맑아라",
                "username": "soo12345",
                "createdAt": "2023-09-09T20:48:35.496913",
                "modifiedAt": "2023-09-09T20:48:35.496913"
            }
        ]
    },
    {
        "id": 5,
        "title": "1번",
        "content": "난리 난리",
        "username": "soo12345",
        "createdAt": "2023-09-09T20:46:34.400005",
        "modifiedAt": "2023-09-09T20:46:34.400005",
        "commentList": [
            {
                "id": 9,
                "content": "내일은 일요일",
                "username": "soo12345",
                "createdAt": "2023-09-09T20:48:44.300948",
                "modifiedAt": "2023-09-09T20:48:44.300948"
            }
        ]
    },
    {
        "id": 3,
        "title": "4번",
        "content": "빗창이 블루 싱글",
        "username": "soo12345",
        "createdAt": "2023-09-09T09:48:30.392599",
        "modifiedAt": "2023-09-09T09:49:30.421203",
        "commentList": [
            {
                "id": 5,
                "content": "초코맛",
                "username": "soo12345",
                "createdAt": "2023-09-09T20:48:09.048717",
                "modifiedAt": "2023-09-09T20:48:09.048717"
            },
            {
                "id": 6,
                "content": "더운 날씨",
                "username": "soo12345",
                "createdAt": "2023-09-09T20:48:17.889308",
                "modifiedAt": "2023-09-09T20:48:17.889308"
            }
        ]
    },
    {
        "id": 4,
        "title": "4번",
        "content": "빗창이",
        "username": "soo12345",
        "createdAt": "2023-09-09T09:48:44.493182",
        "modifiedAt": "2023-09-09T09:48:44.493182",
        "commentList": []
    }
]

위에는 포스트맨으로 게시글과 해당 관련 댓글을 전체조회한 결과다

 

[
    {
        "id": 6,
        "title": "2번",
        "content": "오늘도 마무리",
        "username": "soo12345",
        "createdAt": "2023-09-09T20:46:45.601216",
        "modifiedAt": "2023-09-09T20:46:45.601216",
        "commentList": [
            {
                "id": 7,
                "content": "돌고돈다",
                "username": "soo12345",
                "createdAt": "2023-09-09T20:48:26.101369",
                "modifiedAt": "2023-09-09T20:48:26.101369"
            },
            {
                "id": 8,
                "content": "내일도 맑아라",
                "username": "soo12345",
                "createdAt": "2023-09-09T20:48:35.496913",
                "modifiedAt": "2023-09-09T20:48:35.496913"
            }
        ]
    },
    {
        "id": 3,
        "title": "4번",
        "content": "빗창이 블루 싱글",
        "username": "soo12345",
        "createdAt": "2023-09-09T09:48:30.392599",
        "modifiedAt": "2023-09-09T09:49:30.421203",
        "commentList": [
            {
                "id": 5,
                "content": "초코맛",
                "username": "soo12345",
                "createdAt": "2023-09-09T20:48:09.048717",
                "modifiedAt": "2023-09-09T20:48:09.048717"
            },
            {
                "id": 6,
                "content": "더운 날씨",
                "username": "soo12345",
                "createdAt": "2023-09-09T20:48:17.889308",
                "modifiedAt": "2023-09-09T20:48:17.889308"
            }
        ]
    },
    {
        "id": 4,
        "title": "4번",
        "content": "빗창이",
        "username": "soo12345",
        "createdAt": "2023-09-09T09:48:44.493182",
        "modifiedAt": "2023-09-09T09:48:44.493182",
        "commentList": []
    }
]

5번 게시글을 삭제한 후 결과다

게시글 DB에는 5번 게시글이 삭제 되었고 

댓글  DB에도 마친가지로 해당 댓글인 9번이 삭제 되었다.

 

 

CascadeType.REMOVE를 쓸때도 마찬가지였다.

둘의 공통점은 부모 엔티티를 삭제하면 자식 엔티티도 같이 삭제가 된다는 공통점이 있다.

의도했든 하지 않았든 데이터는 삭제가 된다.

 

다만 다른점은 부모와 자식간의 연관관계를 제거 했을 때는

  • CascadeType.REMOVE = 자식 엔티티인 댓글이 삭제 되지 않고 DB에 남아있고 왜래키 값만 변경이 된다.
  • orphanRemoval = true는 자식 엔티티인 댓글이 고아 객체가 되어 DB에서 삭제가 된다.

또한 부모와 자식간의 연관관계를 변경했을 때는 

  • CascadeType.REMOVE 
  • orphanRemoval = true

위의 두 옵션 모두 자식 엔티티인 댓글이 DB에 삭제되지 않고 남아있고 외래키 값만 변경이 된다.

그래서 위의 2가지 옵션을 사용할 때는 주의가 필요하다.

자식 엔티티를 삭제할 상황이 아니어도 삭제가 되기 때문이다.

 

그래서 기술매니저님께서 하신 말씀 중에 sofe delete에 대해 공부해 보라고 하셨다.

찾아보니 삭제에 대해서 2가지 방법이 있었다.

  • 물리삭제(Hard delete)
    • SQL에서 DELETE 명령어를 사용해서 직접적으로 데이터를 삭제하는 방법이라고 한다.
    • 삭제하려는 데이터가 더이상 필요가 없을 때 사용한다.
  • 논리삭제(Soft delete)
    • SQL에서 UPDATE 명령어를 사용해서 삭제여부를 알수 있는 컬럼에 데이터가 삭제되었다는 값을 넣어서 표현한다고 한다.
    • 삭제하려는 데이터를 보관해야할 때 사용한다.

데이터를 삭제할 때 실수로 삭제를 해버렸다면 백업을 따로 해놓지 않았다면 되돌리기 힘들다.

하지만 Soft delete를 사용한다면 실수로 삭제했던 결과를 되돌릴 수 있다.

이번 과제에서는 강의에 나온대로 Hard delete를 사용해서 게시글과 해당 댓글들을 삭제했지만 앞으로는 

데이터를 삭제할때 잘 선택해서 삭제해야 겠다고 생각했다.

 

 

 

 

참고한 게시글

https://velog.io/@yuseogi0218/JPA-CascadeType.REMOVE-vs-orphanRemoval-true

 

JPA - CascadeType.REMOVE vs orphanRemoval = true

CascadeType.REMOVE 와 orphanRemoval = true 옵션이 각각 고아객체를 어떻게 처리하는지 알아보았습니다.

velog.io

https://blogeon.tistory.com/entry/Soft-Delete%EB%85%BC%EB%A6%AC-%EC%82%AD%EC%A0%9C%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C

 

Soft Delete(논리 삭제)에 대해서

Soft Delete를 알게된 배경 HRD 해커톤을 진행하며 프로젝트에서 게시판 API 개발을 담당하였고 CRUD 로직을 작성하고 있었습니다. 이전의 프로젝트에서는 단순하게 JPA의 delete() 메서드를 사용했기 때

blogeon.tistory.com

https://velog.io/@taeha7b/hard-delete-softdelete

 

물리삭제(hard delete)와 논리삭제(soft delete)

데이터베이스에서 데이터를 삭제하는 방법에는 물리삭제와 논리삭제가 있으며 그중 하나를 선택하여 사용합니다.

velog.io