나를 기록하다
article thumbnail
반응형

2. 즉시 로딩과 지연 로딩

  • 단순히 member 정보만 사용하는 비즈니스 로직
  • member 클래스
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;

→ 멤버 클래스만 db에서 조회한다는 뜻

지연로딩 LAZY

  • JpaMain
Member m = em.find(Member.class, member1.getId());
  • 결과
Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_3_0_,
        member0_.INSERT_MEMBER as INSERT_M2_3_0_,
        member0_.createdDate as createdD3_3_0_,
        member0_.UPDATE_MEMBER as UPDATE_M4_3_0_,
        member0_.lastModifiedDate as lastModi5_3_0_,
        member0_.TEAM_ID as TEAM_ID7_3_0_,
        member0_.USERNAME as USERNAME6_3_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?

→ 멤버만 가져온다

  • JpaMain
try {

            Team team = new Team();
            team.setName("teamA");
            em.persist(team);

            Member member1 = new Member();
            member1.setUsername("member1");
            member1.setTeam(team);
            em.persist(member1);

            em.flush();
            em.clear();

            Member m = em.find(Member.class, member1.getId());

            System.out.println("m = " + m.getTeam().getClass());

            tx.commit();
        }
  • 결과
Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_3_0_,
        member0_.INSERT_MEMBER as INSERT_M2_3_0_,
        member0_.createdDate as createdD3_3_0_,
        member0_.UPDATE_MEMBER as UPDATE_M4_3_0_,
        member0_.lastModifiedDate as lastModi5_3_0_,
        member0_.TEAM_ID as TEAM_ID7_3_0_,
        member0_.USERNAME as USERNAME6_3_0_ 
    from
        Member member0_ 
    where
        member0_.MEMBER_ID=?
m = class hellojpa.Team$HibernateProxy$bVsFQaGi

team을 사용하기 전까지는 조회 x(프록시)

Member member = em.find(Member.class, 1L);

team을 사용하는 시점에 초기화

Team team = member.getTeam();
team.getName(); // 실제 team을 사용하는 시점에 초기화(DB조회)

Member와 Team을 자주 함께 사용한다면? → 즉시 로딩(EAGER)를 사용해서 함께 조회

@Entity
public class Member {
  @Id
  @GeneratedValue
  private Long id;
  @Column(name = "USERNAME")
  private String name;
@ManyToOne(fetch = FetchType.EAGER) //** @JoinColumn(name = "TEAM_ID")
  private Team team;
..
}

즉시로딩 - EAGER

→ JPA 구현체는 가능하면 조인을 사용해서 SQL을 한번에 함께 조회하려 함!

프록시와 즉시로딩 주의

1. 가급적 지연 로딩만 사용(특히 실무에서)

적은 테이블에서는 sql 구문이 조금 더 나가는 차이겠지만 수십, 수백개의 테이블에서는 성능 차이가 심하게 난다.

2. 즉시 로딩을 적용하면 예상하지 못한 SQL 발생

3. 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.

try {

            Team team = new Team();
            team.setName("teamA");
            em.persist(team);

            Member member1 = new Member();
            member1.setUsername("member1");
            member1.setTeam(team);
            em.persist(member1);

            em.flush();
            em.clear();

            List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();

            tx.commit();
        }
  • select 구문이 두 번 나가는 이유
    1. select m from Member m 이 구문이 SQL로 번역(SQL: select * from Member)
    2. 구문을 실행할 때 Team 값이 있어야 함 → SQL: select * from Team where TEAM_ID = xxx
Hibernate: 
    /* select
        m 
    from
        Member m */ select
            member0_.MEMBER_ID as MEMBER_I1_3_,
            member0_.INSERT_MEMBER as INSERT_M2_3_,
            member0_.createdDate as createdD3_3_,
            member0_.UPDATE_MEMBER as UPDATE_M4_3_,
            member0_.lastModifiedDate as lastModi5_3_,
            member0_.TEAM_ID as TEAM_ID7_3_,
            member0_.USERNAME as USERNAME6_3_ 
        from
            Member member0_
Hibernate: 
    select
        team0_.TEAM_ID as TEAM_ID1_7_0_,
        team0_.INSERT_MEMBER as INSERT_M2_7_0_,
        team0_.createdDate as createdD3_7_0_,
        team0_.UPDATE_MEMBER as UPDATE_M4_7_0_,
        team0_.lastModifiedDate as lastModi5_7_0_,
        team0_.name as name6_7_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
  • N+1인 이유 → 처음 쿼리(select m from Member m)를 1개 날렸는데 그것 때문에 N개의 추가 쿼리가 나가기 때문에 N+1 문제라고 함.
  • 해결 방법
    1. 패치 조인(기본): 동적으로 내가 원하는 애들을 선택해서 한방에 가져오는 것.
    2. 엔티티 그래프와 어노테이션
    3. 배치 사이즈(1+1)

4. @ManyToOne, @OneToOne은 기본이 즉시 로딩 → LAZY로 설정

5. @OneToMany, @ManyToMany는 기본이 지연 로딩

3. 지연 로딩 활용

이론적인 것 - 실무에서는 무조건 지연 로딩!!

  • Member와 Team은 자주 함께 사용 → 즉시 로딩
  • Member와 Order는 가끔 사용 → 지연 로딩
  • Order와 Product는 자주 함께 사용 → 즉시 로딩

지연로딩(LAZY)와 즉시로딩(EAGER)의 이론적인 활용 방법 - 실무에서는 지연로딩만 사용

지연 로딩 활용 -실무

  • 모든 연관관계에 지연 로딩을 사용해라!
  • 실무에서 즉시 로딩을 사용하지 마라!
  • JPQL fetch 조인이나, 엔티티 그래프 기능을 사용!
  • 즉시 로딩은 상상하지 못한 쿼리가 나간다!

4. 영속성 전이: CASCADE

정의

특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때

예시

부모 엔티티를 저장할 때 자식 엔티티도 함께 저장

예시) cascade = CascadeType.ALL을 설정하면 em.persist(parent)만 해도 child1, child2까지 함께 persist 된다.

try {

            Child child1 = new Child();
            Child child2 = new Child();

            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);

            tx.commit();
        }
@Entity
public class Parent {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> childList = new ArrayList<>();
Hibernate: 
    /* insert hellojpa.Parent
        */ insert 
        into
            Parent
            (name, id) 
        values
            (?, ?)
Hibernate: 
    /* insert hellojpa.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)
Hibernate: 
    /* insert hellojpa.Child
        */ insert 
        into
            Child
            (name, parent_id, id) 
        values
            (?, ?, ?)

영속성 전이: 저장

@OneToMany(mappedBy="parent", cascade=CascadeType.PERSIST)

영속성 전이(Cascade.PERSIST) 시 parent에 @OneToMany로 연관관계 매핑된 child1, child2가 함께 영속화(persist)가 된다. / h2 데이터베이스에 적용 화면

CASCADE 주의 사항

  1. 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다.
  2. 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐이다.
  3. 소유자가 하나일 때만 사용 가능. ex) parent와 child에서 child를 parent만 사용할 때는 가능하지만 다른 클래스에서도 child를 사용하면 cascade를 사용하면 안된다.

CASCADE 종류(ALL, PERSIST만 대부분 사용)

ALL: 모두 적용(대부분) 

PERSIST: 영속(저장할 때만 사용할 때)

REMOVE: 삭제

MERGE: 병합

REFRESH: REFRESH

DETACH: DETACH

5. 고아 객체

기능

고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제

orphanRemoval = true
Parent parent1 = em.find(Parent.class, id); parent1.getChildren().remove(0);
//자식 엔티티를 컬렉션에서 제거
DELETE FROM CHILD WHERE ID=?

예시코드

try {

            Child child1 = new Child();
            Child child2 = new Child();

            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);

            em.flush();
            em.clear();

            Parent findParent = em.find(Parent.class, parent.getId());
            findParent.getChildList().remove(0);

            tx.commit();
        }

연관관계가 끊어진 고아객체는 컬렉션에서 제거된다.

→ orphanRemoval은 관계가 끊어진 자식을 컬렉션에서 제거한다(em.flush()와 em.clear()로 값 넘긴 후 findParent.getChildList().remove(0)을 통해 0번지 값의 연관관계를 끊자 컬렉션에서도 제거됨.

주의사항

1. 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능

2. 참조하는 곳이 하나일 때 사용해야 한다.

3. 특정 엔티티가 개인 소유할 때 사용

4. @OneToOne, @OneToMany만 가능

5. 참고: 개념적으로 부모를 제거하면 자식은 고아가 된다.

→ 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.

  • 예시코드
try {

            Child child1 = new Child();
            Child child2 = new Child();

            Parent parent = new Parent();
            parent.addChild(child1);
            parent.addChild(child2);

            em.persist(parent);
            em.persist(child1);
            em.persist(child2);

            em.flush();
            em.clear();

            Parent findParent = em.find(Parent.class, parent.getId());

            em.remove(findParent);
            tx.commit();
        }
@Entity
public class Parent {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "parent", orphanRemoval = true)
    private List<Child> childList = new ArrayList<>();
Hibernate: 
    select
        childlist0_.parent_id as parent_i3_2_0_,
        childlist0_.id as id1_2_0_,
        childlist0_.id as id1_2_1_,
        childlist0_.name as name2_2_1_,
        childlist0_.parent_id as parent_i3_2_1_ 
    from
        Child childlist0_ 
    where
        childlist0_.parent_id=?
Hibernate: 
    /* delete hellojpa.Child */ delete 
        from
            Child 
        where
            id=?
Hibernate: 
    /* delete hellojpa.Child */ delete 
        from
            Child 
        where
            id=?
Hibernate: 
    /* delete hellojpa.Parent */ delete 
        from
            Parent 
        where
            id=?

결과: 자식까지 다 지워진다.

6. 영속성 전이 + 고아 객체, 생명주기

1. CascadeType.ALL + orphanRemoval=true

2. 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거

3. 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있음

4. 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용

7. 실전 예제 - 5. 연관관계 관리

  • @OneToOne 또는 @OneToMany만 cascade ALL 설정
반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...