이어달리기 프로젝트에서 로그인 부분 얼추 마무리 단계에 접어들면서 회원 탈퇴 API를 맡게 되었다.

처음에 Repository로 하나씩 조회하려고 했으나, 이번 프로젝트 ERD 설계에서 테이블마다 FK로 얽히고 설켜있기에,,

Entity 설계할 때 cascade 설정을 놓치고 있었음을 깨달았다.

 

이번 포스팅은 양방향 연관관계에서 쓰이는 영속성 전이 cascade에 대해 알아볼 것이다!

 

우선 그 전에 회원 탈퇴 API를 구현하면서 맞닥뜨린 에러문을 보자.

Cannot delete or update a parent row: a foreign key constraint fails ...

 

이런 에러문이 떴다.

해석하자면 해당 데이터 (row)를 삭제하려면 FK 관계에 있는 컬럼 때문에 삭제나 업데이트가 불가능해지는 것이다.

즉, 데이터 테이블 간 종속성 때문에 생기는 에러다.

 

해당 에러를 해결하기 위해서는 

 

1. 자식 테이블의 정보를 먼저 삭제 후, 부모 테이블의 정보를 삭제한다.
2. on delete Cascade 문법을 통해 부모 테이블이 삭제될 때 종속된 자식 테이블도 같이 삭제한다.

 

1번의 경우 테이블 설계가 복잡해질시 골치 아프다.

그리고 코드가 깔끔하지 않다 ..

따라서 2번의 방법으로 선택했다.

 

엔티티를 설계할 때 cascade (영속성 전이) 옵션을 추가하면 된다.

우선 영속성 정의 그리고 고아 객체에 대해 알아보자!

 


Cascade (영속성 전이)

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

 

Cascade의 종류

  • ALL : 모두 적용 
  • PERSIST : 영속 (부모만 영속화하면 설정한 자식 엔티티까지 함께 영속화해서 저장)
  • MERGE : 병합
  • REMOVE : 삭제 (부모 엔티티만 삭제하면 연관된 자식 엔티티도 함께 삭제)
  • REFRESH : Refresh
  • DETACH : DETACH

 

Orphan (고아 객체), OrphanRemoval

JPA에서는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데 이를 고아 객체 제거라 한다.

OrphanRemoval을 이용하면 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제된다!

이때 주의점은 특정 엔티티가 소유하는 엔티티에만 이 기능을 적용해야한다.

만약 삭제한 엔티티를 다른 곳에서도 참조한다면 문제가 발생할 수 있다.

그러기에 @OneToOne, @OneToMany에만 사용할 수 있다!!

 

 

CascadeType.REMOVE 와 orphanRemoval = true의 차이

이 둘은 관계가 끊어졌을 때 데이터에 대한 동작의 차이이다.

orphanRemoval = true는 연관된 엔티티 간의 참조가 끊어질 때 삭제가 이뤄진다.

cascade = CascadeType.REMOVE 는 부모 엔티티를 삭제하면 자식 엔티티를 삭제하는 것이지 참조가 끊어질 때 삭제가 이뤄지는 것은 아니다.

orphanRemoval = true 는 자식 객체의 데이터까지 제거하는 반면, CascadeType.REMOVE 는 자식 엔티티가 그대로 남아있다. 참조를 변경하여 무결성 오류를 안나게 할 뿐, 데이터는 남겨둔다.

 

따라서 두 엔티티의 관계를 끊을 때, 테이블의 데이터가 계속 남아있기를 바란다면, CascadeType.Remove 만 쓰는 것이고,

테이블의 데이터까지 삭제를 바란다면 orphanRemoval = true 를 사용하는 것이다.

 

 

Cascade + Orphan (영속성 전이 + 고아 객체)

일반적으로 엔티티는 EntityManager.persist()를 통해 영속화하고 remove()를 통해 제거되며 엔티티 스스로 생명주기를

리한다.

하지만 CascadeType.ALL + orphanRemoval = true 를 동시에 사용하면 부모 엔티티를 통해 자식의 생명 주기를 관리할 수 있다!!

 

 

 


이어달리기 프로젝트의 ERD 설계를 살펴보자.

User 엔티티 삭제할 때 => User 테이블 참조하는 UserProfile 엔티티를 삭제하게 되는데, 

이때 UserProfile을 FK로 삼는 Group 부분에서 에러가 발생했다.

 

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long clubIdx;

@Column(nullable = false, length = 20)
private String name;

@Column(nullable = false, length = 50)
private String content;

@Column(columnDefinition = "text")
private String imgURL;

@OneToOne
@JoinColumn(name = "userProfileIdx")
private UserProfileEntity hostIdx;

그룹 엔티티 일부인데, hostIdx(방장)를 UserProfile에서 참조하고 있다.

 

UserProfile - Club (그룹)은 일대일 연관관계으로 엮여있기에

@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "hostIdx", orphanRemoval = true)
private ClubEntity club;

다음과 같이 UserProfile 엔티티(PK 테이블)의 club 컬럼에서 cascade과 orphanRemoval 설정을 추가하였다.

 

User - UserProfile 은 일대다 연관관계로 엮여있기에

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "userProfileIdx", orphanRemoval = true)
private List<UserProfileEntity> userProfileEntities = new ArrayList<>();

 

부모 테이블에 자식 테이블들 연관 관계를 설정하고, 영속성 전이와 고아 객체 설정을 완료하면

부모 테이블이 삭제됨과 동시에 해당 자식 테이블들도 삭제된다!!

 

 

다만 테이블을 이미 생성한 상태에서 수정하려면

테이블을 create함과 동시에 이 설정들이 적용되기에 drop과 함께 초기화해야 한다..!

 

하지만

-- 삭제하는 SQL

ALTER TABLE 테이블명 DROP FOREIGN KEY 테이블_FK값;

ALTER TABLE feed DROP FOREIGN KEY feed_ibfk_3;

먼저 기존에 있었던 FK들을 제거하고

 

-- ALTER를 이용한 CASCADE 추가
ALTER TABLE 테이블명
ADD CONSTRAINT 제약조건명
  FOREIGN KEY (CHILD_테이블의_FK값) -- 해당 테이블의 FK 설정
  REFERENCES MOTHER_테이블명(MOTHER_테이블의_PK) -- MOTHER PK와 연결
  ON DELETE CASCADE; -- MOTHER TABLE의 값 삭제시 연결된 값 삭제

다음과 같은 sql문으로 Cascade 제약 조건을 추가하면 된다.

이때 FK를 빈 상태로 두면 자동으로 생성된다.

 

아니면 스키마에 직접 cascade 설정을 추가하면 된다!

 

 

이렇게 영속성 전이와 고아 객체를 통해 에러를 해결할 수 있었다.

강의만 듣고 막연했던 부분인데, 이렇게 트러블 슈팅을 통해 고민하며 Entity 연관관계에 있어 공부해볼 수 있어 좋았다!

+ Recent posts