• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

영속성 전이 질문입니다..

23.01.04 04:29 작성 23.01.04 04:31 수정 조회수 2.62k

0

안녕하세요. 강의 잘 듣고 있습니다.

저는 책과 같이 병행하며 학습을 진행 중입니다..

 

책 313 page에 보면.. 영속성 전이 관련 설명 중..

Parent parent = em.find(Parent.class, parentId);

parent.addChild(child1);

 

코드의 의미가 궁금합니다..

제가 이해하기론 영속성 전이는 특정 엔티티가 영속 상태로 변할 때,

연관된 엔티티도 영속 상태로 만들게 해주는 기능으로..

 

책의 코드와 내용만 봤을 땐, parent를 find해서 이미 영속 상태로 만들어 두고..

child1과 parent를 addChild 편의 메서드를 통해 연관관계를 설정해주면

flush 단계에서 변경감지와 영속성전이..? 를 통해 child가 db에 적재 될 것으로 생각하게 되었습니다..

 

하지만.. 제가 예상하기로는

-> parent Entity 입장에서는 양방향 연관관계도 설정했고 cascade를 걸어놨지만, 영속상태로 전환되는 상황이 아니라서 child1이 db에 적재되지 않을 것으로 예상했습니다.

-> child1 Entity 입장에서는 연관관계 주인이며 양방향 연관관계 설정이 되었지만.. 비영속 상태라 변경 감지가 이루어지지 않아서 db에 업데이트 되지 않을 것으로 예상했습니다. (책에선 child1을 저장하기 위함이라 설명)

그래서 제가 예상한 개념과 차이가 생겨서 실습도 해봤지만.. db에 적재가 되지 않는 것으로 확인 했습니다..

 

아래는 스스로 진행한 실습 코드입니다. (cascadeType.ALL)

Parent parent = new Parent();
parent.setName("p1");

Child child1 = new Child();
child1.setName("c1");


System.out.println("==========1===========");
entityManager.persist(parent);
System.out.println("==========2===========");

entityManager.flush();
entityManager.clear();;

System.out.println("==========3===========");
entityManager.find(Parent.class, parent.getId());
System.out.println("==========4===========");

parent.addChild(child1);

//entityManager.merge(parent);

System.out.println("==========5===========");

tx.commit();

 

다음은 질문 정리입니다.

  1. 제가 예상한 점들이 맞는지 궁금합니다.

  2. 책 속 코드의 의도와 의미가 궁금합니다.

  3. 혹시, em.merge(parent); 가 생략된 것이 의미와 의도에 맞는 건지 궁금합니다.

    1. merge를 하면 child1이 db에 적재가 되는데.. 엔티티의 생명주기에서 parent는 managed 상태에서 merge를 하는 지금과 같은 상황은 어떤 의미 인지도 궁금합니다..

  4. 제가 무엇을 잘못 생각하고 있을까요.. ㅠ

 

항상 감사합니다. ^^7

답변 1

답변을 작성해보세요.

0

안녕하세요. starryeye님

cascade ALL(PERSIST)에서는 parent가 persist 될때 자식도 함께 persist 됩니다.

변경 감지(더티 체킹)에서는 이 부분이 동작하지 않습니다.

casecade ALL에는 MERGE 옵션이 있으므로 이때도 함께 동작합니다.

감사합니다.

starryeye님의 프로필

starryeye

질문자

2023.02.03

안녕하세요. 좀더 공부를 하고 다시 왔습니다.

제 질문을 좀 더 설명 드리자면..

아래의 캡쳐 속 설명은 책에 영속성 전이와 고아 객체를 설명하는 곳에 있습니다.

(313 page)

image

제가 이해가 안되는 부분은 "자식을 저장하려면 부모에 등록만 하면 된다…" 입니다.

 

영속성 전이는 부모가 persist 될 때 자식도 persist 되는 것으로

말 그대로 전이가 되는 것으로 알고 있습니다.

 

구체적으로..

책에서는 find로 부모는 이미 영속성 상태로 진입한 상태이고

자식은 부모가 먼저 영속성 상태에 진입한 후, addChild로 추가 해주고 있기 때문에

전이가 이루어지지 않는 것이 아닌가 합니다. (이전 첫번째 질문에서 코드로 확인)

 

그래서, 책속에서 “자식을 저장하려면 부모에 등록만 하면 된다” 라는

설명과 코드를 봤을 때 오해가 좀 생기게 되었습니다.

 

그래서 제가 잘못 생각하고 있는 부분이 있는지..

확인 받고 싶습니다.

 

늘 감사합니다.

 

ps. 이번에 20만 기념으로

식사권이나.. 멘토링에 당첨 되길 굉장히 기도 했는데 아쉽게 되었습니다.. ㅎㅎ

야근을 한다고 생방으로는 못 봤는데 뒤늦게 올라온 풀영상을 봤습니다.

직접 답변해주신 부분 명심하겠습니다!

 

 

안녕하세요. starryeye님^^

20만 기념 QA올려주셔서 감사합니다.

책에서 나온 내용대로 관리가 되는 것이 맞습니다.

실행한 코드를 보여드릴께요.

package com.example.demo.entity;

import jakarta.persistence.*;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;


@Entity
@Data
public class Parent {

    @Id
    @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
    private List<Child> children = new ArrayList<>();

    public void addChild(Child child) {
        child.setParent(this);
        children.add(child);
    }
}

 

package com.example.demo.entity;

import jakarta.persistence.*;
import lombok.Data;

@Entity
@Data
public class Child {

    @Id @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "parent_id")
    private Parent parent;
}

 

package com.example.demo.entity;

import jakarta.persistence.EntityManager;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import static org.junit.jupiter.api.Assertions.*;

@DataJpaTest
class ParentTest {

    @Autowired
    EntityManager em;

    @Test
    void cascadeByFind() {
        Parent parent = new Parent();
        em.persist(parent);
        em.flush();
        em.clear();

        Child child = new Child();
        Parent findParent = em.find(Parent.class, parent.getId());
        findParent.addChild(child);
        em.flush();
    }
}

 

SQL 실행 결과

Hibernate: select next value for parent_seq

Hibernate: insert into parent (id) values (?)

Hibernate: select p1_0.id from parent p1_0 where p1_0.id=?

Hibernate: select next value for child_seq

Hibernate: insert into child (parent_id, id) values (?, ?)

마자막에 child가 insert 되는 것을 볼 수 있습니다.

감사합니다.

 

 

starryeye님의 프로필

starryeye

질문자

2023.02.12

안녕하세요 선생님,

코드 비교 중 깨달았습니다.. 감사합니다..

 

아래는 제가 생각하고있는 개념입니다..

틀린 부분 있으면 지적해주시면 감사하겠습니다.

 

persist()는 호출하는 순간 엔티티를 영속 상태로 저장한다.

find()는 호출하는 순간 db에서 엔티티를 꺼내와서 영속 상태로 저장한다.

 

cascade(persist)는

어떤 엔티티가 영속 상태라면 그 엔티티에 비영속 엔티티를 add하면

비영속 엔티티도 같이 영속화 된다.

  • 어떤 엔티티가 영속화 "될 때만" 같이 영속화 되는 것이 아님

  • 이미 영속화 된 상황에서도 동일하게 처리된다.

 

아래 분석한 것과 같이 진행되어서 child.setParent(this) 코드가 없어도 fk가 null인채로

그대로 영속화 되어 버린다. (그래서 연관 관계 주인과 관련 없는 것도 확인)

아래는 직접 디버그 모드로 간단하게 따라가다가 관련된 부분을 디버깅해봤습니다.

flush 준비 과정 중..

Cascade.class의 아래 cascade 메서드에서

현재 영속상태인 parent 엔티티에 cascade 속성이 있는지 확인 후,

해당 프로퍼티(children)를 찾아서

가지고있는 비 영속 상태인 child도 영속상태로 만듭니다.

public static void cascade(CascadingAction action, CascadePoint cascadePoint, EventSource eventSource, EntityPersister persister, Object parent, Object anything)

이후, 실제 db반영 시점에 parent에 풀려있던 lock 해제하고 영속화된 child 도 db에 반영

(JPA lock 관련 학습은 하지 않았지만.. 조회 시점부터 commit 시점까지 parent 가 db에서 조회 되지 않아서 추측합니다.)

 

 

 

 

아래는 제가 오해하게되었던 코딩실수입니다..

(최초 Test 코드 작성시 잘못된 점 때문에 제가 오해하게 되었습니다.)

 

아래 코드가 잘못 되었던 .. ㅠㅠ

entityManager.find(Parent.class, parent.getId());

parent.addChild(child1);

 

리턴 값을 받아서 addChild를 호출 해야 하는데

그냥 find만 해놓고 있었습니다..

그 결과.. 준영속 상태인 parent 에다가 자꾸 addChild를 한 것이었습니다.

그래서 merge를 해야 child가 db에 반영되었습니다.

 

 

 

starryeye님 코드로 끝까지 확인해보신 부분을 잘 하셨습니다^^