[JPA] 8. 프록시와 연관관계 관리 - 즉시 로딩, 지연로딩, 영속성 전이

24723 단어 JPAJPA

[JPA] 8. 프록시와 연관관계 관리

자바 ORM 표준 JPA 프로그래밍 공부 기록



📍 8.2 즉시 로딩과 지연 로딩

프록시 객체는 주로 연관된 엔티티를 지연 로딩 할때 사용한다.

JPA는 연관된 엔티티의 조회 시점을 선택할 수 있도록 지연로딩, 즉시로딩 두 가지 방법을 제공한다.

💭 지연 로딩 (LAZY를 사용해서 프록시로 조회)

Member 엔티티

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    private String userName;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="TEAM_ID")
    private Team team;
    ...

테스트 코드

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

Member m = new Member();
m.setUserName("HELLO");
m.setTeam(t);
em.persist(m);

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

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

System.out.println(findMember.getTeam().getClass());
System.out.println("===========================");
System.out.println(findMember.getTeam().getName()); // 실제 team을 사용하는 시점에 초기화(DB 조회)
tx.commit();

출력 결과

Hibernate: 
    select
        member0_.id as id1_3_0_,
        member0_.TEAM_ID as TEAM_ID3_3_0_,
        member0_.userName as userName2_3_0_ 
    from
        Member member0_ 
    where
        member0_.id=?
class com.jpa.db.Team$HibernateProxy$4y1KwunM //반환된 객체는 프록시 객체
===========================
Hibernate: 
    select
        team0_.id as id1_5_0_,
        team0_.name as name2_5_0_ 
    from
        Team team0_ 
    where
        team0_.id=?
teamA
  • Member findMember = em.find(Member.class, m.getId());
    호출하면 Member만 조회하고 Team은 조회하지 않는다.
    대신 Team 멤버변수에 프록시 객체를 넣어둔다.
  • findMember.getTeam().getClass()시 반환된 객체는 프록시 객체이다. 이 객체는 실제 사용될 때 까지 데이터 로딩을 하지 않는다. => 지연 로딩

조회한 Team 엔티티를 실제 사용하는 시점에 JPA가 SQL를 호출해서 팀 엔티티를 가져온다.


💭 즉시 로딩 (EAGER를 사용해서 함께 조회)

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

Member 엔티티

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    private String userName;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="TEAM_ID")
    private Team team;
    
    ...
    

테스트 코드

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

Member m = new Member();
m.setUserName("HELLO");
m.setTeam(t);
em.persist(m);

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

Member findMember = em.find(Member.class, m.getId()); //조인을 사용해서 SQL 한번에 함께 조회

System.out.println(findMember.getTeam().getClass());
System.out.println("===========================");
System.out.println(findMember.getTeam().getName()); 
tx.commit();

출력 결과

Hibernate: 
    select
        member0_.id as id1_3_0_,
        member0_.TEAM_ID as TEAM_ID3_3_0_,
        member0_.userName as userName2_3_0_,
        team1_.id as id1_5_1_,
        team1_.name as name2_5_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.TEAM_ID=team1_.id 
    where
        member0_.id=?
class com.jpa.db.Team
===========================
teamA
  • Member findMember = em.find(Member.class, m.getId());
    호출 시 Member 뿐만 아니라 Team도 함께 조회한다. 이 때 Member와 Team을 조인해서 쿼리 한 번으로 두 엔티티를 모두 조회한다.



💭 즉시 로딩 주의

  • 실무에서 가급적 지연 로딩 사용
    즉시 로딩은 예상치 못한 SQL이 발생한다
    또한 JPQL에서 N+1 문제를 일으킨다
  • @ManyToOne, @OneToOne은 기본이 즉시로딩이다 => LAZY로 변경



💭 지연 로딩 활용

  • 모든 연관관계에 지연 로딩을 활용
  • 실무에서는 즉시 로딩 사용 X



📍 8.4 영속성 전이 : CASCADE

연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 CASCADE 옵션을 사용하면 된다.

ex) 부모 엔티티 저장 시 자식 엔티티도 함께 저장

Parent, Child Entity

@Entity
public class Parent {

    @Id
    @GeneratedValue
    @Column(name="PARENT_ID")
    private Long id;

    private String name;

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

    public void addChild(Child child){
        childList.add(child);
        child.setParent(this);
    }
    
    ...
    
    
    
@Entity
public class Child {
    @Id
    @GeneratedValue
    private Long id;

    private String name;

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

테스트 코드

Child c1 = new Child();
Child c2 = new Child();

Parent p = new Parent();
p.addChild(c1);
p.addChild(c2);

//부모 저장, 연관된 자식도 함께 저장
em.persist(p);

💭 CASCADE 옵션 종류

ALL : 모두 적용
PERSIST : 영속
REMOVE : 삭제
MERGE : 병합
REFRESH : REFRESH
DETACH : DETACH



📍 8.5 고아 객체

부모 엔티티와 연관 관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능
=> 고아 객체(ORPHAN) 제거

부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제

@Entity
public class Parent {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "parent", orphanRemoval = true)
    private List<Child> childList = new ArrayList<>();

childList 컬렉션에서 제거한 엔티티는 자동으로 삭제된다고 한다.
=> 그러나 테스트 해봤을 때 orphanRemoval = true만으로는 컬렉션에서 첫 번째 자식을 제거했을 때 데이터베이스의 데이터가 삭제 되지 않았다.
관련 자료 : https://github.com/jyami-kim/Jyami-Java-Lab/issues/1


부모를 제거하면 자식은 고아가 된다. 따라서 orphanRemoval = true 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.



📍 8.6 영속성 전이 + 고아 객체, 생명주기

CascadeType.ALL + orphanRemoval = true 을 둘다 활성화하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.

  • 자식을 저장하려면 부모에 등록만 하면 된다 (CASCADE)
  • 자식을 삭제하려면 부모에서 제거하면 된다 (orphanRemoval)



좋은 웹페이지 즐겨찾기