[자바 Spring] JPA의 CasCade에 대해 알아보자
일반적으로 JPA의 기능 중 하나인 CasCade는 부모에서 자식에게 영속성을 전이하는 기능을 제공한다고 알려져 있습니다.
영속성을 제공한다는 말이 어려울 수 있는데 쉽게 말해 개발자에 의해 부모 클래스가 생성되거나 삭제된다면 연결되어 있는 자식 클래스도 자동적으로 생성되거나 삭제된다고 이해하면 될 것 같습니다.
*JPA내에서도 @OneToMany, @ManyToOne과 같은 매핑 어노테이션의 어트리뷰터 값으로 cascade를 사용할 수 있습니다.
# CasCaded의 종류
1. CascadeType.PERSIST
Entity가 추가 될 때, 연관된 Entity도 추가합니다.
2. CascadeType.MERGE
Entity가 수정 될 때, 연관된 Entity도 수정한다.
3. CascadeType.REFRESH
Entity를 새로 고칠 때, 연관된 entity도 재갱신합니다.
4. CascadeType.REMOVE
Entity를 삭제할 때, 연관된 Entity도 삭제됩니다.
5. CascadeType.DETACH
부모 Entity가 detach()를 수행하게 되면, 연관된 Entity도 detach() 상태가 되어 변경사항이 반영되지 않습니다.
6. CascadeType.ALL
모든 Cascade 적용
# CasCade의 예시
CasCade 중에서도 가장 빈번하게 사용되는 PERSIST를 통해 설명하도록 하겠습니다.
@NoArgsConstructor
@Getter
@Setter
@Entity
public class Member extends Auditable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column(nullable = false, updatable = false, unique = true)
private String email;
@Column(length = 100, nullable = false)
private String name;
@Column(length = 13, nullable = false, unique = true)
private String phone;
@OneToOne(mappedBy = "member", cascade = CascadeType.PERSIST)
private Stamp stamp;
public void setStamp(Stamp stamp) {
this.stamp = stamp;
if (stamp.getMember() != this) {
stamp.setMember(this);
}
}
}
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Stamp extends Auditable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long stampId;
@Column(nullable = false)
private int stampCount;
@OneToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
public void setMember(Member member) {
this.member = member;
if (member.getStamp() != this) {
member.setStamp(this);
}
}
}
위에 나오는 두 개의 엔티티 클래스, "Member" 와 "Stamp"를 활용하여 예시를 들어보겠습니다. 만약 제가 하나의 카페를 운영하고 있다면 "Member" 클래스는 이름 그대로 저희 카페의 회원들을 등록하는 클래스로 정의할 수 있을 것 입니다.
저희 카페는 고객 유치를 위한 프로모션으로 스탬프 쿠폰을 각각의 고객들에게 지급하려고 합니다. 스탬프 쿠폰을 10개 찍으면 커피가 1회 공짜인 것처럼 사용하기 위해서 말이죠
그래서 저희는 고객과 스탬프 쿠폰의 관계를 JPA를 통해 1:1로 연결하였습니다. 고객1의 쿠폰이 갑자기 고객2에게 적용되거나 하는 일이 발생하지 않도록 한명의 고객 당 하나의 쿠폰만 지정될 수 있도록 하였습니다.
이것이 위 코드에서 보이는 @OneToOne 입니다. 두 엔티티 클래스를 1:1로 연결하여 고객이 자신만의 쿠폰을 가지게 하는 기능을 가지고 있습니다.
하나의 "Member" 클래스는 반드시 하나의 "Stamp" 클래스는 서로 연결되어야만 합니다.
때문에 저희는 "Member" 클래스를 만들면 자동으로 그 고객에게 맞는 단 하나의 "Stamp" 클래스를 생성하고자 하였고 그 해결책이 바로 CasCade입니다.
"PostMan" 어플리케이션을 이용하여 하나의 Member 클래스를 생성해 보겠습니다. 결과는 아래와 같이 정상적으로 고객 정보가 저장된 것을 확인할 수 있습니다.
고객 정보를 저장한 것으로 끝을 낸다면 데이터베이스에는 Member 클래스 테이블만 생성되어 있어야 정상입니다. 다른 클래스들은 생성하지 않았으니 말이죠
그러나 실제 데이터 베이스에는 "Member"와 "Stamp", 두개의 클래스가 생성되어 있을 것입니다.
위 두개의 테이블 중 Stamp 테이블을 유심히 보면 테이블의 필드에 '1' 이라는 값을 가지는 MEMBER_ID를 확인할 수 있을 것입니다. 이 값은 아래 Member 테이블의 MEMBER_ID와 같고 이것은 두 테이블이 서로 1:1 연결되어 있다는 것을 알려줍니다.
분명 한개의 Member 클래스만 데이터베이스에 저장했는데 Stamp 클래스까지 저장된 이유가 바로 CasCade입니다.
@OneToOne(mappedBy = "member", cascade = CascadeType.PERSIST)
private Stamp stamp;
맨 위 Member 클래스를 정의할 때 필드 값으로 있었던 해당 코드가 결과적으로 Stamp 테이블까지 생성한 것으로 이해하면 될 것 같습니다. PERSIST 대신에 REMOVE가 들어가면 MEMBER 테이블이 제거될 때 STAMP 테이블도 제거되는 것을 확인하실 수 있을 것입니다.