Cascade의 역할을 제대로 알지 못해 힘들었던 며칠 🥲 눙물이 납니다..
그래도 덕분에 Entity mapping에 대해서도 좀 더 잘 알게되고 Entity와 Dto 간 mapping도 여러번 수정해 보며
어떻게 하는 것인지 좀 더 알게된 시간이었습니다.
먼저 Cascading이 언제 필요한 것인지 보면,
Cascading은 두 entity의 존재가 서로에게 의존적일 때 필요합니다. 예를 들어서, Member-Address라는 관계가 있다고 했을 때 Address는 Member가 있어야만 그 의미를 갖게 됩니다. (Member 정보는 없는데 주소만 있다면 누구의 주소를 의미하는지 그 쓰임이 무색해질테니까요) 그렇기 때문에 Member 정보를 삭제할 때 그와 관계된 Address 정보도 같이 삭제되어야 합니다.
이와 같은 관계에서 Cascading이 사용되는 것입니다. 한 Entity에 적용되는 행위가 다른 한 쪽에도 동일하게 적용이 되게 됩니다.
여기서 동일한 행위가 두 entity에게 모두 적용된다고 하였는데, 생성도 마찬가지인 것 같더라구요. 한 entity(table)가 생성이 되면 cascade가 적용된 다른 entity(table)도 같이 생성이 되는 것입니다. entity의 인스턴스는 직접 생성해주어야 합니다. 전에는 cascade가 적용된 entity(table)은 해당 entity를 멤버변수로 갖는 entity가 생성되면 자동으로 생성된다고 오해를 했었는데, 아니더라구요. 다시한번 cascade의 strategy를 보니
- ALL
- PERSIST
- MERGE
- REMOVE
- REFRESH
- DETACH
이렇게 종류가 있는데, 이 중에 persist를 entity를 생성해주는 역할도 같이 한다고 착각을 했었습니다.
persist는 말그대로 persistence context에 persist 즉, '저장' 해주는 명령입니다. 객체를 생성해 주는 것이 아니구요! 그러니 cascade strategy 중에는 객체 생성을 해주는 것은 없었던 것입니다.
근데 왜 자동으로 생성된다고 생각했던 걸까요?
비밀은 바로 Controller에 있었습니다. post method 안에 cascade 관계에 있는 객체를 만드는 코드가 들어있었던 것입니다.
아래 코드처럼요.
member.setEFrequency(new EFrequency());
이렇게 적고 나서 entity mapping에서 cascade에 대해 설명을 하다보니 위의 코드를 잊었던 것 같습니다. 😂
그래도 다행히 연습을 또 해보면서 원래 알고 있던 사실과 다른 현상이 나오게 되어 한 번 더 해당 부분에 대해 공부해 보고
정확한 사실을 알게되어 다행이라고 생각합니다. 알게되는데까지 시간이 좀 걸렸지만요 ㅎㅎ
그래서 언제 cascade를 사용해야 하는가에 대해 제가 내린 결론은 "post로 직접 생성이 되지 않는 entity가 있을 때 사용한다" 입니다.
"한 entity의 존재에 영향을 받는 entity가 있을 때 사용한다" 입니다. 영속성 컨텍스트와 DB에 저장이 같이되고, 한 entity가 삭제되면 같이 덩달아 삭제되어야 하고 등등 다른 entity의 존재에 영향을 받는 경우에 사용하면 됩니다.
아래와 같이 두개의 entity가 있는데,
Member가 생기면 자동적으로 EFrequency도 생기는 관계입니다. 하지만 EFrequency만은 생성할 방법이 없습니다.
이럴 때 Memeber쪽에 선언된 eFrequency field에 Cascade를 설정하면, Member 데이터가 생성될 때 자동적으로 eFrequency를 생성할 수 있게 됩니다.
코드로는 이렇게 작성해보았습니다.
[Member class]
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Entity
public class Member {
@Id
@GeneratedValue
Long memberId;
@Column
String name;
//cascade 설정해서 EFrequency가 자동으로 생성되도록 설정
@OneToOne(mappedBy = "member", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
EFrequency eFrequency;
public Member(String name) {
this.name = name;
}
//객체 간 연관관계 설정 시 참조무결성을 위해 아래와 같이 setter 작성
public void seteFrequency(EFrequency eFrequency) {
this.eFrequency = eFrequency;
if(eFrequency.getMember() != this) {
eFrequency.setMember(this);
}
}
}
[EFrequency class]
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Entity
public class EFrequency {
@Id
@GeneratedValue
Long eFrequencyId;
@Column
int frequencyCount;
@OneToOne
@JoinColumn(name = "MEMBER_ID")
Member member;
//객체 간 연관관계 설정 시 참조무결성을 위해 아래와 같이 setter 작성
public void setMember(Member member) {
this.member = member;
if(member.getEFrequency() != this) {
member.seteFrequency(this);
}
}
}
Controller도 작성해 준 다음 postman에 쿼리를 보내면
아래와 같이 Member와 EFrequency 테이블에 데이터가 잘 들어갔음을 확인할 수 있습니다.
지난번에 이와 같은 상황에서 Cascade 설정을 하지 않았더니, Member 객체 생성 시에 Member와 EFrequency 테이블에 모두에 데이터가 들어가기는 하는데 EFrequency의 MEMBER_ID column이 null로 들어갔었습니다.
동일한 상황을 재현해보려는데 단순히 Cascade attribute만 지우니 아래와 같이 에러가 발생하더라구요.
한 번 조금 더 코드를 만져보면서 지난번과 같이 MEMBER_ID에 어떻게 하면 null이 들어가게 될 지 살펴봐야겠습니다.
==> Update
지난번에 발견하지 못한 어떻게하면 MEMBER_ID에 null이 들어가게 하는지 알게되었습니다.
Entity간 mapping은 제대로 해두되 기본 setter를 사용하면 그렇게 되더라구요. Member entity 객체가 생성될 때 Stamp 객체도 생성해서 set해주는데, 객체간 참조무결성을 고려해서 set되는 Stamp객체에도 해당 Member 객체 정보를 등록해 주어야 합니다. 그렇지만 기본 setter를 사용하니 Stamp에는 해당 Member 정보가 저장되지 않아서 그랬던 것이었습니다.
궁금증이 또 하나 해결되었습니다. ㅎㅎ <== End
Cascade에는 몇 가지 종류가 있는데, 아래 참고 자료에 상세하게 나와있으니 참고해보셔도 좋을 것 같습니다.
Cascade도 종류별로 사용해보면서 어떤 상황에 어떤 종류를 사용하는 것이 가장 적절할 지 알아보고 추가적으로 포스팅을 업데이트 해보도록 하겠습니다 ☺️
감사합니다.
ps. 혹시 H2 DB에 담긴 데이터를 보면 member 테이블의 id가 왜 1, 2, 3 이렇게 안 생성되고 1, 3, 5로 생성되는지 아시는 분 계실까요? (EFrequency Id는 2, 4, 6 이렇게 생성이 되고..)
=> 이에 대한 해답을 찾았습니다. GeneratedValue의 strategy 때문에 그랬습니다. strategy를 따로 설정하지 않고 default로 적용되게 했었는데,
strategy = GenerationType.IDENTITY
로 설정하니 member id도 1, 2, 3 순서대로 생성되고, eFrequency id도 1, 2, 3 이렇게 순차적으로 생성되는 것을 확인할 수 있었습니다.
참고로 default strategy는 GenerationType.AUTO입니다.
참고자료
[객체 간 참조 무결성 관련 자료]
[Types of Cascasding]
https://howtodoinjava.com/hibernate/hibernate-jpa-cascade-types/
'Java Spring > MVC' 카테고리의 다른 글
Spring Rest Docs 만들기 (1) | 2022.07.19 |
---|---|
Mockito 적용하여 Controller test 해보기 (0) | 2022.07.18 |
[Spring] Spring JDBC 사용하기 (0) | 2022.07.02 |
[Spring] H2 in-memory DB 사용하기 (0) | 2022.06.28 |
[Spring] Servlet과 Servlet Container (0) | 2022.06.24 |