ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] 즉시로딩과 지연로딩 Eager And Lazy
    프로그래밍/JPA 2022. 1. 13. 01:06
    반응형

    이 글은 혼자 학습한 내용을 바탕으로 작성되었습니다.

    틀리거나 잘못된 정보가 있을 수 있습니다.

    댓글로 알려주시면 수정하도록 하겠습니다.


    1. 즉시 로딩과 지연 로딩

    엔티티에서 연관관계를 가지는 엔티티가 있는 경우 이 연관된 엔티티를 어떤 시점에 조회를 할 것인지 결정하는 로딩 방법을 말합니다.

     

    • 즉시 로딩 - 엔티티를 조회할 때 연관된 엔티티를 같이 조회하는 로딩 방법
      ManyToOne으로 연관된 Team 엔티티를 즉시 로딩으로 설정하게 되면 Member 엔티티를 조회하는 시점에 연관된 Team 엔티티를 조회하는 로딩 방법입니다.

    • 지연 로딩 - 엔티티를 조회할 때 연관된 엔티티를 같이 조회하는 것이 아닌 연관 엔티티의 사용 시점에 조회하는 로딩 방법
      ManyToOne으로 연관된 Team 엔티티를 지연 로딩으로 설정하게 되면 Member 엔티티를 조회하는 시점이 아닌 team.name과 같이 team 엔티티를 사용할 때 해당 team에 대한 조회를 수행하는 로딩 방법입니다.

     

    2. 로딩 방법이 필요한 이유

    Java 객체의 경우 연관된 객체의 속성이나 메서드 등 연관 객체를 탐색할 수 있습니다.

     

    예로 Member객체의 연관된 Team의 팀명을 알고자 하는 경우 member.team.name과 같이 연관된 객체의 데이터를 읽어올 수 있습니다.

     

    JPA의 엔티티의 경우도 결국 Java의 객체이므로 해당 엔티티에 연관된 엔티티를 탐색하거나 값을 가져올 수 있어야 합니다.

     

    그럼 모든 연관된 엔티티를 엔티티를 조회하는 시점에 가져오면 해결되는 것이 아닐까 하는 생각을 할 수 있습니다.

     

    하지만 모든 연관된 엔티티 수가 작고 다대일, 일대다와 같은 다수의 엔티티 개수가 적다면 별 문제가 되지 않을 수 있지만 만약 연관된 엔티티의 수가 많고 다대일, 일대다와 같은 다수의 엔티티 개수가 많다면 서버는 엔티티 하나를 조회하기 위해 많은 부하가 발생하게 됩니다.

     

    또한 사용하고자 하는 엔티티가 조회한 엔티티인 경우 연관된 엔티티는 조회의 필요가 없습니다.

     

    이런 경우 지연 로딩을 사용하여 연관된 엔티티는 사용 시점에 조회하여 사용하고자 하는 엔티티만 조회하여 부하를 줄일 수 있습니다.

     

    3. 프록시 객체

    지연 로딩에서 엔티티의 조회 시점에 실제 연관 엔티티가 아닌 사용 시점에 실제 엔티티를 조회하여 연관 엔티티의 데이터를 가져온다고 하였습니다.

     

    이때 엔티티를 조회하면 연관된 엔티티는 실제 엔티티가 아닌 프록시 객체를 가지고 있는 엔티티가 조회됩니다.

     

    프록시란 대신이라는 의미를 가지고 있습니다. 프록시 엔티티는 결국 무언가 대신하는 엔티티를 의미하는 것을 느낌적(?)으로 알 수 있습니다.

     

    지연 로딩으로 조회한 연관 엔티티는 프록시 객체로 가지고 있다 연관된 엔티티 조회 시 프록시 객체가 실제 엔티티를 조회하여 사용할 수 있도록 해주는 중간 역할을 담당합니다.

     

    OneToMany, ManyToMany와 같이 연관된 엔티티가 Collection인 경우 Hibernate에서는 PersistentBag이라는 프록시 Collection으로 만들어 전달하여 줍니다.

     

    4. 즉시 로딩 구현

    즉시 로딩을 구현을 위해 EagerMember Entity와 EagerTeam Entity를 생성하였습니다.

     

    Member와 Team은 ManyToOne으로 Member -> Team 연관관계를 표현하였으며 로딩 방법을 즉시 로딩으로 설정하였습니다.

     

    반대로 Team -> Member는 OneToMany로 연관관계를 표현하였으며 로딩 방법 또한 즉시 로딩으로 설정하였습니다.

     

    각 Entity를 저장하고 조회하기 위한 Repository를 생성하였습니다.

     

    Member, Team Service Class의 경우는 지연 로딩과 즉시 로딩 두 방법 모두 서비스하는 Class로 생성하였습니다.

     

    즉시 로딩 Test의 경우 즉시 로딩으로 설정된 EagerMember Entity를 조회한 뒤 연관된 EagerTeam Entity의 Class가 EagerTeam Class인지 프록시 객체인지 비교하는 Test를 수행하였습니다.

     

    Test는 성공적으로 종료되는 것을 보아 즉시 로딩으로 조회된 EagerTeam Entity의 경우 프록시가 아닌 것을 확인할 수 있었습니다.

     

    위 결과의 이유는 즉시 로딩의 경우 EagerMember Entity 조회와 동시에 EagerTeam Entity를 생성하기 때문에 Team의 프록시 객체가 아닌 실제 Team Entity 객체가 조회되는 것을 확인할 수 있습니다.

     

    이번에는 조회 SQL을 살펴보도록 하겠습니다.

     

    SQL을 보면 EagerMember만 조회한 것이 아닌 Left Outer Join을 통해 EagerTeam을 같이 조회한 것을 확인할 수 있습니다.

     

    조회하고자 하는 Entity와 연관관계에 있는 Entity들 중 즉시 로딩으로 설정된 Entity의 경우 조회와 동시에 연관관계의 Entity들도 조회가 되어야 하므로 Join을 통해 같이 조회를 수행합니다.

     

    Left Outer Join 사용

    즉시 로딩 SQL을 보면 Join이 Inner Join(내부 조인)이 아닌 Left Outer Join(외부 조인)으로 사용한 것을 확인할 수 있었습니다.

     

    내부 조인이 아닌 외부 조인을 사용한 이유는 해당 Column이 현제 Null 값을 허용하기 때문입니다.

     

    Member들 중 Team에 속하지 않은 Member가 존재할 때 내부 조인으로 조회를 하면 문제가 발생합니다.

     

    내부 조인으로 모든 Member를 조회하면 Team에 속하지 않은 Member는 조회가 불가능하기 때문입니다.

     

    그러므로 내부 조인을 쓰기 위해서는 Column의 값을 NotNull로 설정하면 JPA는 외부 조인이 아닌 내부 조인을 사용하여 결과 조회를 진행합니다.

     

    또는 ManyToOne 어노테이션의 Optional 속성 값을 false로 설정하면 내부 조인을 이용하여 조회를 진행할 수 있습니다.

     

    테스트 진행 SQL을 보면 Member 조회 후 또 다른 SQL이 수행된 것을 확인할 수가 있습니다.

     

    쿼리를 해석하면 특정 Team에 속한 Member를 조회하는 쿼리가 실행되었습니다.

     

    이 쿼리가 실행된 이유 또한 EagerTeam Entity의 EagerMember Collection이 즉시 로딩으로 설정되어 있어서입니다.

     

    조회 순서를 보면 먼저 Member의 Id가 3인 Member를 조회합니다. 이때 Member의 Team이 즉시 로딩으로 되어 있으므로 Team을 외부 조인을 통해 같이 조회하여 옵니다. (붉은색 선)

     

    이렇게 조회하고자 하는 Member와 Member에 연관된 Team을 모두 조회 완료하였습니다.

     

    그런데 Team의 Member Collection이 즉시 로딩으로 되어 있습니다. 그러므로 JPA는 해당 Team에 속한 Member를 찾아오기 위해 다시 SQL을 수행하여 Team에 속한 Member들을 조회하게 됩니다. 이때 바로 위 SQL이 수행되는 것입니다. (녹색 선)

     

    5. 지연 로딩 구현

    지연 로딩을 구현을 위해 LazyMember Entity와 LazyTeam Entity를 생성하였습니다.

     

    Member와 Team은 ManyToOne으로 Member -> Team 연관관계를 표현하였으며 로딩 방법을 지연 로딩으로 설정하였습니다.

     

    반대로 Team -> Member는 OneToMany로 연관관계를 표현하였으며 로딩 방법 또한 지연 로딩으로 설정하였습니다.

     

    각 Entity를 저장하고 조회하기 위한 Repository를 생성하였으며, Service는 즉시 로딩과 동일한 Service Class를 이용하였습니다.

     

    지연 로딩 Test의 경우 지연 로딩으로 설정된 LazyMember Entity를 조회한 뒤 연관된 LazyTeam Entity의 Class가 LazyTeam Class인지 프록시 객체인지 비교하는 Test를 수행하였습니다.

     

    Service에서는 이미지의 SQL을 통해 Member를 조회하였습니다.

     

    그리고 Debugging을 통해 member 변수의 속성을 확인하였습니다.

     

    Debugging 결과 lazyTeam 속성의 Class를 보면 알 수 있듯 HibernateProxy Class 타입으로 전달된 것을 확인할 수 있습니다. 또한 현재 lazyTeam의 속성 값이 0과 모두 null인 것을 알 수 있습니다.

     

    메소드의 2번째 줄의 명령을 수행하여 위 SQL문이 수행되었습니다.

     

    member.getLazyTeam().getName()

    즉시 로딩과 다르게 지연 로딩의 경우 조회하고자 하는 Member를 먼저 조회하여 Entity로 Mapping 한 뒤 해당 Entity의 Team 속성의 값을 사용하는 시점(위 명령어)에 DB에서 Team을 조회하여 팀명을 가져오는 것을 확인할 수 있습니다.

     

    또한 Team의 Member Collection에 대한 조회가 수행되지 않은 것을 확인할 수 있습니다.

     

    member.getLazyTeam().getMemberList().size()

    이 조회 또한 Team의 Collection을 사용하는 시점(위 명령어)에 DB에서 Member들을 조회하여 Member 수를 반환하는 것을 예측할 수 있습니다.

     

    2번째 Test의 경우 Team을 사용하지 않는 Test이므로 Member에 대한 조회만 수행하고 Team에 대한 조회는 수행하지 않는 것을 확인할 수 있습니다.

     

    또한 Team은 프록시 Class 타입으로 저장되어 있는 것을 확인할 수 있습니다.

     

    사용된 예제 코드는 Git에 있습니다.

    반응형

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

    [JPA] N+1 문제  (0) 2022.03.02
    [JPA] Enum 사용  (0) 2022.01.14
    [JPA] Attribute Converter  (0) 2022.01.12
    [JPA] Dirty Checking(변경 감지)  (0) 2021.12.24
    [JPA] 예외 처리  (0) 2021.11.05

    댓글

Designed by Tistory.