과제하다가 갑자기 DELETE 가 동작하지 않았다....ㅋㅋㅋㅋㅋㅋ... 따라서 여러가지 찾아보다가 이렇게 글을 쓰게 되었습니다...
ERROR: update or delete on table "member" violates foreign key constraint "fkid4wmieido0slha6oaqc8l5fb" on table "blog"
Detail: Key (id)=(6) is still referenced from table "blog".
다음과 같은 Error로인해서 동작하지 않는다고 한다. 잘 읽어보면 update나 delete 시 member가 blog의 fk를 위반하고 있다고 한다. 따라서 blog참조하려다가 error가 떴다고 확인할수 있었다.
그럼 이전에 먼저 ManyToOne , OneToMany에 대한 기본적인 내용은 아래 글에서 확인할수 있다.
https://kiru-dev-study.tistory.com/12
@ManytoOne, @ManytoMany, @OnetoOne, @OnetoMany?
3차 세미나를 진행하면서 DB, JPA에 대해서 간단한 이론과 실습을 진행하면서 여러가지 의문점들을 느껴 찾아찾아 들어가다 보니 여기까지 왔네용...일단 제가 느낀 "의문점"에 대해서 설명드리고
kiru-dev-study.tistory.com
CASCADE
관계형 데이터베이스를 다루다보면 활용할 일이 많은 Cascade! (Cascade는 ‘폭포수가 흐르다’ 라는 사전적 의미를 갖고 있습니다.)
참조의 관계를 맺은 데이터베이스를 "신뢰성"있는 상태로 유지하기 위해서 사용을 한다.
즉 데이터베이스상의 Primary Key와 Foreign Key간의 관계가 항상 유효하도록 관리를 하는 것이며 PK를 참조하는 FK가 있다면 해당 PK는 수정과 삭제가 불가능하다는 "참조무결성"을 가진 데이터베이스를 유지하도록 하는것이다.
즉 부모 entity를 제거시에 연관된 다른 테이블의 자식 entity까지 한꺼번에 지워줄수 있는 "영속성 관리"를 cascade를 활용하여 진행할수 있다. 그러나 이러한 관계에서 단방향 , 즉 부모자식이 서로 한쪽만 알고있을경우 위와 같이 제거할수 없다는 error 메세지를 받게 된다. 내가 하고싶은건 Member를 삭제하면 에러가 터지는게 아니라 Member가 가지고 있는 Blog, 그리고 Blog에 연결된 Post까지 싹다 삭제되게 하고 싶은 것!! 그렇다면 어떻게 이러한 문제를 해결해야할까?
우리가 진행한 Entity를 한번 보자 .
간단히 얘기하면 Member는 Blog와 1:1 관계이며 Blog는 Post와 1:Many 관계를 맺고 있다. 대부분의 연관관계 설정시 ManyToOne을 사용해 항상 많은쪽에 자식쪽에서 부모쪽으로 이어주는 것이 좋다고 얘기해 자식 entity쪽 다 연관관계를 설정해놓았었다.
public class Member extends BaseTimeEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@NotBlank
private String name;
@Enumerated(EnumType.STRING)
private Part part;
@Min(7)
private int age;
}
public class Blog extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter
@OneToOne(fetch = FetchType.LAZY)
private Member member;
@Setter
@Column(length = 200)
private String title;
@Setter
private String description;
}
블로그는 @OneToOne의 관계를 Member를 참조하는 단방향관계로 이어주었다. 문제는 여기서 Blog table 에서는 FK로 Member를 알수 있지만 Member table에서는 Blog를 알수 없다는 문제가 있다. 따라서 부모인 Member쪽에서도 참조할수 있게 지정을 해줘야한다.
하지만 이런 CASCADE 의 6가지 Type이 있는데 각 table의 연관관계를 제대로 알고 CASCADE를 사용해야 한다. 그렇지 않으면 양방향 연관관계를 맺고 있으므로 자식을 삭제시에 연관된 부모를 삭제해버릴 수도 있는 일이 발생할수 도있다.
PERSIST
먼저 Persist는 엔티티를 영속화할때 연관된 entity도 함께 영속화하는 옵션이다. 예를 들어 부모객체인 Member를 영속화 한다고 했을때 Blog가 있을시 연관된 Blog까지 함께 영속화를 시킨다.
Merge
엔티티 상태를 병합할때 연관된 entity도 함께 병합하는 옵션이다. 예를 들면 @Transactional 이 붙은 메소드안에서 Member와 Blog를 둘다 상태를 변경하고 transactional이 끝나 Member의 entity의 update시 바로 Blog상태도 같이 변경이 되는것이다.
REMOVE
엔티티를 제거할때 연관된 entity들도 함께 제거한다. 이걸 가장 위험하면서 잘써야하는 keyword며 항상 조심하면서 써야한다.
REFRESH
엔티티를 새로고침할시 , 즉 데이터베이스로부터 실제 레코드의 값을 즉시 로딩해 덮어씌우면 연관된 entity도 새로고침한다는 것
DETACH
엔티티를 영속성 컨텍스트로부터 분리하면 연관된 entity들도 분리된다는 것이다.
ALL
위 언급한 모든 CasCade Type이 적용된다.
CasCade에 대해 요약하자면 해당 작업이 수행되면 cascade로 묶인 다른 entity로 이러한 속성이 "전파"된다고 생각하면 된다. 하지만 이런 CasCade를 사용시 @ManyToOne으로 묶여 특수한 경우가 아니라면 자식쪽에서 절대로 부모쪽의 caseCade를 설정하면 안된다.
그렇다면 위 코드로 일단 delete를 날렸을때의 결과를 보자.
localhost:8080/api/v1/member/:memberId
Detail: Key (id)=(6) is still referenced from table "blog".
member를 삭제할려고했는데 blog의 FK 때문에 변경이 되지 않는다. 따라서 Member쪽에서도 조회할수있게 코드를 변경해보도록 하겠다. 다음과 같이 Member쪽에서도 조회할수있게 양방향 설정을 해주고 다시 DELETE를 실행해보자.
public class Member extends BaseTimeEntity{
...
@OneToOne(mappedBy = "member", cascade = CascadeType.REMOVE)
private Blog blog;
}
Detail: Key (id)=(5) is still referenced from table "post".
엇 이제 Blog와 연관된 Post table쪽에서 FK를 갖고있기 때문에 제거할수 없다고 나온다.
public class Post extends BaseTimeEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
@Setter
private String title;
@Setter
@Column(nullable = false)
private String content;
@ManyToOne(fetch = FetchType.LAZY)
private Blog blog;
}
위 코드처럼 Post에서는 Blog를 참조할수있지만 Blog에서는 참조할수 없으므로 Blog에서 Post를 참조할수 있게 @OneToMany 로 연관성을 추가해 보겠다. 아래 처럼 blog에서 REMOVE가 실행될시 연관된 Post도 삭제하는 CASCADE로 지정을 하고 다시 delete를 날려보자.
@OneToMany(mappedBy = "blog",cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<Post> posts = new ArrayList<>();
로그를 보면 아래 처럼 먼저 타고들어가 Post객체 삭제, blog객체 삭제 , member 객체 삭제 순서로 삭제되는 것을 볼수 있다.
데이터베이스 특성상 연관된 하위객체가 존재할시 상위 객체를 삭제할수 없고 CASCADE를 통해서 하위 객체부터 삭제되는 것을 볼 수 있다.
위와 같이 무사히 삭제를 할수 있지만 한번에 연관된 것들이 사라질수 있다는 위험이 있기 때문에 table의 확실한 연관관계를 알고 있지 못한다면 cascade를 사용하는 것을 지양하는 것이 좋다고 한다.
'Spring' 카테고리의 다른 글
Spring 검색조회 필터링 구현 방법 [JPA Specification] (0) | 2024.07.31 |
---|---|
편리한 객체간 매핑을 위한 MapStruct 적용기 .feat 당근클론코딩 (1) | 2024.05.08 |
@ManytoOne, @ManytoMany, @OnetoOne, @OnetoMany? (2) | 2024.04.28 |
SpringBoot와 Docker Container :Postgresql 연결하기! (2) | 2024.04.22 |
데이터베이스에 더미 데이터를 추가하는 방법 (5) | 2024.04.19 |