6. [JPA] 영속성 전이 - Cascade

프로그래밍|2017. 8. 17. 16:43

영속성 전이에 대해 이해하기 위해서는 영속성 컨텍스트의 선행 학습이 필요하다.

영속성 컨텍스트가 뭔지 잘 모르겠다면 http://lng1982.tistory.com/273 페이지 읽기를 추천한다. (내가 쓴 글을 내가 추천하니 뭔가 이상하다.)


영속성 전이라는 용어가 다소 생소할 것이다.

예를 들어 Member, MemberPhone 엔티티 객체가 존재한다고 하자.

Member엔티티를 엔티티매니저를 통해 영속화하면 MemberPhone엔티티도 함께 영속 상태가 되는데 이를 두고 영속성 전이라고 한다. 

이처럼 둘의 엔티티가 영속 상태가 되었다면 영속성 컨텍스트가 flush될 때 DB에 insert문이 전송된다.


JDBC로 개발 했을 때에는 부모가 되는 Member 테이블에 insert를 먼저 하고 그 다음에 MemberPhone 테이블에 insert를 하는 식으로 개발했을 것이다. 이와는 다르게 JPA는 엔티티의 객체 연관관계를 맵핑하고 값만 잘 채워넣은 후 em.persist(member); 와 같이 단 한 줄의 코드로 영속 상태의 엔티티를 만들 수 있다.


영속성 전이를 위한 Cascade 설정에는 여러 가지 옵션이 존재한다.

옵션은 CascadeType enum에 정의 되어 있으며 enum값에는 ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH 가 있다.


테스트 코드를 통해 영속성 전이가 어떻게 동작하는지 알아보자.


Member클래스와 HomeAddress클래스를 작성한다.

복잡하지 않게 두 클래스간의 연관 관계는 1:1 이다.

JPA에서는 1:1 관계를 @OneToOne 애노테이션을 이용하여 표현한다.


실제 테이블 모양은 다음처럼 되어 있으니 참고하길 바란다.

JPA를 처음 접하게 되면 객체 연관관계만 봐서는 이해하기 쉽지 않을 것이다. (내 경험상)

그러니 DB 테이블 구조와 대조해 보는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
create table test_cascade_home_address (
    home_address_id integer not null,
    address varchar(255),
    member_id integer,
    primary key (home_address_id)
)
 
create table test_cascade_member (
    member_id integer not null,
    name varchar(255),
    primary key (member_id)
)
 
alter table test_cascade_home_address 
add constraint FKh0bdfsvahvux2qar39s7ci780 
foreign key (member_id) 
references test_cascade_member
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Data
@Entity
@Access(AccessType.FIELD)
@Table(name = "TEST_CASCADE_MEMBER")
class Member {
 
    @Id
    @Column(name = "MEMBER_ID")
    private int id;
 
    @Column(name = "NAME")
    private String name;
}
 
@Data
@ToString(exclude = "member")
@Entity
@Table(name = "TEST_CASCADE_HOME_ADDRESS")
class HomeAddress {
 
    @Id
    @Column(name = "HOME_ADDRESS_ID")
    private int id;
 
    @Column(name = "ADDRESS")
    private String address;
 
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "MEMBER_ID"// 조인 컬럼을 명시하지 않으면 member_member_id로 정의된다.
    private Member member;
}
 
cs

아래는 테스트 데이터를 저장하기 위한 코드이다.

Member 엔티티 객체에 아이디와 이름을 넣었다.

HomeAddress 엔티티 객체에도 아이디와 지역을 넣고 Member 엔티티 객체를 set해줬다.

em.persist(homeAddress); 를 실행하면 HomeAddress와 Member 엔티티 객체는 영속상태가 된다.

em.persist(member); 로 해도 HomeAddress와 Member 엔티티 객체는 영속상태가 된다.


persist 메서드에 HomeAddress 엔티티 객체가 아규먼트로 넘어갔는데 Member 엔티티 객체가 어떻게 영속상태가 되냐고?

해답은 HomeAddress 엔티티 클래스의 @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) 설정에 있다.

CascadeType.ALL을 통해 영속성 전이가 이뤄졌기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void memberTestData() {
    // member
    Member member = new Member();
    member.setId(1);
    member.setName("남규");
 
    // home address
    HomeAddress homeAddress = new HomeAddress();
    homeAddress.setId(1);
    homeAddress.setAddress("경기도");
    homeAddress.setMember(member);
 
    System.out.println("Start Test Data ---------------------------------------");
    em.persist(homeAddress); // save
    em.flush(); // DB 반영
    em.clear(); // 영속성 컨텍스트 초기화
    System.out.println("End Test Data ---------------------------------------");
}
 
cs


영속성 전이가 잘 되었는지 검증 해보자.

1
2
3
4
5
6
7
8
9
10
11
12
@Test
@Transactional
public void Member_엔티티저장상태() {
    memberTestData();
 
    HomeAddress homeAddress = em.find(HomeAddress.class1);
    assertThat("경기도", is(homeAddress.getAddress()));
    assertThat("남규", is(homeAddress.getMember().getName()));
 
    Member member = em.find(Member.class1);
    assertThat("남규", is(member.getName()));
}
cs


마지막으로 CascadeType.ALL 에 대한 위험성을 언급하려고 한다.

DB에 테이블이 10개가 있다고 하자.

테이블 10개가 모두 엔티티와 맵핑되고 객체 연관 관계 설정에 모두 CascadeType.ALL 이 추가되어 있다면 어떤 위험이 존재할까?


관계형 DB에서는 테이블이 덩그러니 혼자 있는 경우는 거의 없다.

테이블 각각이 다른 테이블과 관계를 맺고 있으며 무결성 제약 조건을 따른다.

이런 상황에서 A, B, C, D, E, F, G, H, I, J 테이블 중 어떤 한 테이블의 데이터를 remove하면?

의도치 않게 그 데이터와 연관되어 있는 모든 테이블의 데이터는 삭제된다.

이것이 의도된 삭제 전이라면 상관이 없지만 습관적으로 CascadeType.ALL 옵션을 이용하게 되면 문제가 발생할 수 있다라는 것을 말해주고 싶었다.


그래서 난 ALL은 실무에서 안쓰고 다음처럼 사용한다.

1
2
3
@OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
@JoinColumn(name = "MEMBER_ID")
private Member member;
cs



'프로그래밍' 카테고리의 다른 글

10. [JPA] JPQL  (0) 2017.08.28
9. [JPA] @Enumerated  (0) 2017.08.23
8. [JPA] Attribute Converter  (0) 2017.08.23
7. [JPA] fetch type - 로딩 기법  (0) 2017.08.21
5. [JPA] 엔티티 매니저  (0) 2017.08.16
4. [JPA] 엔티티 매니저 팩토리  (0) 2017.08.10
3. [JPA] 영속성 컨텍스트란?  (0) 2017.08.01
2. [JPA] 테스트 환경  (0) 2017.07.21

댓글()