JPA 프록시, 지연로딩, 즉시로딩

김재민·2022년 4월 26일
0

프록시

개요

Member엔티티가 각각 Team이라는 컬럼을 갖고있다. Team은 따로 엔티티로 존재한다.
그렇다면 Member 엔티티를 조회할 때 Team도 함께 조회해야 할까??

  • 실제로 필요한 비즈니스 로직에 따라 다르다.
  • 비즈니스 로직에서 필요하지 않을 때가 있는데, 항상 Team을 가져와서 함께 사용할 필요는 없다.
  • 낭비가 발생하게 됨
  • JPA는 낭비를 하지 않기 위해, 지연로딩(Lazy_loading)과 프록시(Proxy)라는 개념으로 해결

사전적의미

대리(행위)나 대리권, 대리인을 의미 -> 어떤 것을 대신해주는 것

em.find() vs em.getReference()

  • em.find() : DB를 통해 실제 Entity 객체 조회
  • em.getReference() : DB조회를 미루는 가짜(프록시) Entity객체 조회
@Entity
@Getter
@Setter
public class Member extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String username;

    private Integer age;

    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    @Lob
    private String description;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    @OneToOne
    @JoinColumn(name = "locker_id")
    private Locker locker;

    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts = new ArrayList<>();
}
  • em.find()로 멤버를 조회하면 아래와 같이 데이터베이스에 쿼리가 바로 나감
Member member = new Member();
member.setCreatedBy("creator");

em.persist(member);

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

Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getUsername());

tx.commit();

결과

Hibernate: 
    /* insert hello.jpa.Member
        */ insert 
        into
            Member
            (id, createdBy, createdDate, lastModifiedBy, lastModifiedDate, age, description, locker_id, roleType, name) 
        values
            (null, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    select
        member0_.id as id1_4_0_,
        member0_.createdBy as createdB2_4_0_,
        member0_.createdDate as createdD3_4_0_,
        member0_.lastModifiedBy as lastModi4_4_0_,
        member0_.lastModifiedDate as lastModi5_4_0_,
        member0_.age as age6_4_0_,
        member0_.description as descript7_4_0_,
        member0_.locker_id as locker_10_4_0_,
        member0_.roleType as roleType8_4_0_,
        member0_.team_id as team_id11_4_0_,
        member0_.name as name9_4_0_,
        locker1_.id as id1_3_1_,
        locker1_.name as name2_3_1_,
        team2_.id as id1_8_2_,
        team2_.createdBy as createdB2_8_2_,
        team2_.createdDate as createdD3_8_2_,
        team2_.lastModifiedBy as lastModi4_8_2_,
        team2_.lastModifiedDate as lastModi5_8_2_,
        team2_.name as name6_8_2_ 
    from
        Member member0_ 
    left outer join
        Locker locker1_ 
            on member0_.locker_id=locker1_.id 
    left outer join
        Team team2_ 
            on member0_.team_id=team2_.id 
    where
        member0_.id=?
findMember.id = 1
findMember.username = creator
  • em.getReference() : DB조회를 미루는 가짜(프록시) Entity 객체 조회

프록시 객체

실제 Entity를 가리키는 target이라는 값만 들어있음
(최초의 target은 null값 보유)
이후 실제 객체에 접근하는 method(getter)를 호출하면 그 때 실제 DB에 접근해서 target에 실제Entity가 연결됨

1 ) em.getReference()로 호출하여 프록시 객체가 반환( target에는 null이 들어가 있음)
2 ) 실제 객체에 접근하는 member.getName() 호출
3 ) target이 null이기 때문에 영속성 컨텍스트에 요청
4 ) 영속성 컨텍스트는 실제 DB에서 조회한 후 target에 실제 Entity를 연결
5 ) 프록시 객체의 target을 통해 .getName() 호출됨

프록시 특징 및 주의점

  • 프록시 객체 초기화는 최초 사용시 처음에만 수행

  • 프록시 객체 초기화시 프록시 객체가 실제 Entity로 바뀌는 것이 아니다.
    ->프록시 객체의 target에 실제 Entity가 연결될 뿐!

  • 영속성 컨텍스트에 찾는 Entity가 이미 있으면, em.getReference()해도 실제 Entity가 반환됨
    -> JPA는 동일 객체임을 보장하기 위해 프록시를 먼저 조회하면 프록시 객체로 맞추고, 아니면 실제 Entity에 맞추는 내부로직을 가지고 있음
    (그래서 JPA는 하나의 트랜잭션에서 같은 객체의 조회는 항상 동일 객체를 보장함)

  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태에서 프록시 초기화 하면 오류 발생함
    -> 결국 프록시는 영속성 컨텍스트를 통해 일어난다는 것이 핵심.


지연로딩(Lazy)

  • 내부 참조 객체를 실제 사용하는 시점에 조회하는 로딩 방법
  • 내부 참조 객체의 사용을 잘 하지 않는 로직에 효율적(이론적)
    --> 실제 실무에서 모두 Lazy로 설계하고 필요시 한방로딩을 해주는 것이 더 효율적
@ManyToOne(fetch = FetchType.LAZY)

즉시로딩(EAGER)

  • 내부 참조 객체를 조회 시점에 함께 조회하는 로딩 방법

즉시로딩 주의할 점

  • 즉시 로딩을 발생하는 여러가지 문제점들이 있다
  • 예상하지 못한 SQL 실행 : JOIN이 많이 발생되기 때문에 훨씬 더 많은 쿼리가 실행됨
  • JPQL에서 N+1 문제 발생
  • 실무에서 즉시 로딩은 사용하지 말자

profile
어제의 나보다 나은 오늘의 내가 되자!🧗‍♂️

0개의 댓글