[Spring] 트러블 슈팅 - FOREIGN KEY 삭제 관련

sonnng·2023년 5월 24일
0

Spring

목록 보기
5/41

문제상황

Promotion 엔티티와 Likes 엔티티는 1대 N의 관계를 가지며 단방향의 관계를 가지도록 설계하였습니다. 특히 DB에서 PROMOTION 테이블이 삭제되면 마찬가지로 LIKES 테이블 정보도 삭제되어야 합니다. 따라서, @OnDelete(action = OnDeleteAction.CASCADE) 를 추가없이 삭제처리 API를 진행하였습니다. 코드는 다음과 같습니다.

package com.project.unigram.promotion.domain;

import com.project.unigram.auth.domain.Member;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import static javax.persistence.FetchType.LAZY;
import javax.persistence.*;

@Entity
@Getter
@Table(name="likes")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Likes {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //dialect 값에 따른 기본 키 자동 생성 전략을 지정한다.
    @JoinColumn(name="like_id", unique = true, nullable = false)
    private Long likeId;

    @ManyToOne
    @JoinColumn(name="member_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Member member;

    @ManyToOne
    @JoinColumn(name = "promotion_id")
    private Promotion promotion;

    private boolean likeCheck; //true : 좋아요 한 상태, false : 좋아요 안한 상태

    @Builder
    public Likes(Member member, Promotion promotion) {
        this.member = member;
        this.promotion = promotion;
        this.likeCheck = false;
    }

    public void liked(Promotion promotion){
        this.setLikeCheck(true);
    }

    public void unLiked(Promotion promotion){
        this.setLikeCheck(false);
    }
    
    public void setLikeCheck(boolean likeCheck) {
        this.likeCheck = likeCheck;
    }
}

위의 코드처럼 Member 테이블이 삭제되면 Likes 테이블이 삭제되도록 @OnDelete(action = OnDeleteAction.CASCADE)를 작성해주었고 Likes 테이블에는 다른 설정을 하지 않은 상태이었습니다.

그래서 전체 조회 API 를 불러왔을 때나 작성 API를 사용할 경우 문제가 발생하지 않았고 삭제 API를 사용하면서 문제가 됨을 알게 되었습니다.

당연히 에러가 발생하였고 인텔리제이에서의 에러코드는 다음과 같이 나타났습니다.

당연히 PROMOTION 테이블에 속한 외래키값이 사라지니 LIKES 테이블에서는 가리키던 필드가 없어져버린 것이므로 에러가 난 것이라고 볼 수 있습니다.

문제 해결 방법

부모 엔티티 삭제 시 관련된 데이터들을 모두 삭제하는 방법에는 cascade 옵션이 있습니다.

@OneToMany(cascade = CascadeType.ALL)

JPA를 사용하여 cascade 옵션을 줄 때 주로

@OneToMany(mapped="parent", cascade = CascadeType.ALL)

@OneToMany에 cascade 옵션을 줄 수 있습니다. 이 경우 JPA 레벨에 JPA가 부모 엔티티 삭제시 자식 데이터에 대하여 DELETE 쿼리를 실행합니다. 이는 양방향 참조에서 사용하는 방법으로 단방향 참조에서는 사용하기 어렵습니다.

@OnDelete(action = OnDeleteAction.CASCADE)

단방향 매핑에서 사용하기 위해 위의 어노테이션을 사용하였고 다음과 같이 작성하였습니다.

@ManyToOne
@JoinColumn(name = "promotion_id")
@OnDelete(action = OnDeleteAction.CASCADE)
private Promotion promotion;

이 어노테이션을 통해 JPA 레벨이 아니라 DDL 생성시 cascade 옵션을 줌으로써 DB에서 처리하게 됩니다.

여전히 실행되지 않는 경우, 참조 무결성에 의한 에러가 발생해서 인데, properties에서 ddl-auto=update가 아닌 ddl-auto=create로 변경하여야 에러가 생기지 않는다고 합니다.

전체코드

package com.project.unigram.promotion.domain;

import com.project.unigram.auth.domain.Member;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import static javax.persistence.FetchType.LAZY;
import javax.persistence.*;

@Entity
@Getter
@Table(name="likes")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Likes {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //dialect 값에 따른 기본 키 자동 생성 전략을 지정한다.
    @JoinColumn(name="like_id", unique = true, nullable = false)
    private Long likeId;

    @ManyToOne
    @JoinColumn(name="member_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Member member;

    @ManyToOne
    @JoinColumn(name = "promotion_id")
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Promotion promotion;

    private boolean likeCheck; //true : 좋아요 한 상태, false : 좋아요 안한 상태

    @Builder
    public Likes(Member member, Promotion promotion) {
        this.member = member;
        this.promotion = promotion;
        this.likeCheck = false;
    }

    public void liked(Promotion promotion){
        this.setLikeCheck(true);
    }

    public void unLiked(Promotion promotion){
        this.setLikeCheck(false);
    }

    public Long getLikeId() {
        return likeId;
    }

    public Member getMember() {
        return member;
    }

    public Promotion getPromotion() {
        return promotion;
    }

    public boolean isLikeCheck() {
        return likeCheck;
    }

    public void setLikeCheck(boolean likeCheck) {
        this.likeCheck = likeCheck;
    }
}

해결된 postman에서는 잘 동작합니다.

0개의 댓글