OneToOne 연관관계에서 Lazy 로딩 전략이 작동하지 않는 이슈

 

 

들어가며

 

연관관계 주인이 아닌 엔티티쪽에 OneToOne 연관관계 및 Lazy 로딩 전략을 설정한 뒤 연관관계 주인이 아닌 엔티티 조회시 당연하게 Lazy 로딩될줄 알았지만 쿼리로그를 확인해보니 Eager 로딩으로 작동하여 그 이유가 궁금해 포스팅하고자한다.

 

 

이슈 상황

 

다음과 같은 1:1관계의 엔티티들이 존재한다고 가정하자.

한명의 Employee는 한개의 Wallet를 소유 한다.

 

@Entity
class Employee(

    @Id
    @GeneratedValue
    @Column(name = "id")
    val id: Long = 0,
    
    ...
    
    @OneToOne(mappedBy = "employee", fetch = FetchType.LAZY)
    val wallet: Wallet

    ...
)

 

@Entity
class Wallet(

    @Id
    @GeneratedValue
    @Column(name = "id")
    val id: Long = 0,
    
    ...
    
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "employee_id")
    val employee: Employee

    ...
)

 

위와 같이 작성 후 아래 처럼 테스트를 해보자.

 

//employee 프로퍼티는 로딩하지 않는다. (Lazy 로딩 전략이 작동한다.)
val wallet = em.find(Wallet::class.java, id)


//wallet 프로퍼티를 로딩한다. (Eager 로딩 전략이 작동한다.)
val employee = em.find(Employee::class.java, id)

 

Employee 엔티티 내 Wallet 프로퍼티는 Lazy 로딩 전략이 작동하지 않고 Eager 로딩 전략으로 작동한다.

Eager 로딩 전략으로 작동하면 경우에 따라 employee 조회, wallet 조회 쿼리가 각각 비효율적으로 수행되어버린다.

 

@OneToOne(mappedBy = "employee", fetch = FetchType.LAZY)

을 명시했음에도 불구하고 왜 작동하지 않는걸까?

 

 

이유

 

결론적으로 말하면 Employee와 Wallet 간 연관관계 주인은 FK를 가지고 있는 Wallet 이며,
연관관계주인이 아닌 엔티티(Employee)는 Lazy 로딩 전략이 작동하지 않는다.

 

  1. Lazy 로딩 할 엔티티 내 프로퍼티는 엔티티 조회시 프록시 객체를 프로퍼티에 대신 삽입하는데 이 프록시 객체는 null을 감쌀 수 없다.
  2. 그러므로 엔티티내 프로퍼티를 Lazy 로딩시 프록시객체를 만들어 할당 할지 null을 할당할지 결정해야한다.
  3. 이를 결정하는 과정에서 연관관계주인은 문제가 없으나 연관관계주인이 아닌 쪽에서는 문제가 생긴다.
  4. 왜냐하면 FK를 가지고 있는 연관관계주인 엔티티는 FK가 null 값이면 연관관계를 맺는 엔티티가 없음(Wallet 엔티티 내 employee 프로퍼티가 null)을 알 수 있지만, 연관관계주인이 아닌 엔티티쪽은 FK를 가지고 있지 않기 때문에 연관관계를 맺는 엔티티의 존재유무를 알 수 없다.
  5. 따라서 연관관계주인이 아닌 엔티티쪽에서는 프로퍼티를 프록시 객체로 만들어 할당 할지 또는 null로 할당할지, 실제 연관관계를 맺는 엔티티를 조회해볼 수 밖에 없기 때문에 Lazy 로딩 전략이 무시되는것이다.

 

그럼 OneToMany 연관관계에서 One 쪽은 Lazy 로딩 전략이 어떻게 가능한것일까?

 

OneToOne 연관관계에서는 Lazy 로딩시 연관관계되는 엔티티를 null 또는 프록시 객체로 할당하지만, OneToMany 관계에서는 One이 Many쪽을 참조할때 타입은 List다. List는 값이 비어있다(Empty)로 표현이 가능하기때문에 이는 null이 아니므로 프록시객체로 감쌀 수 있기 때문이다.

 

 

해결

 

Lazy 로딩전략이 Eager 로딩 전략으로 작동되는 바람에 쿼리가 2개 이상 수행된다면 1개의 쿼리로 조회 할 수 있도록

 

  • Fetch join (spring data jpa만 사용하고있다면 JPQL 작성해야함) 사용
  • 쿼리 메소드 마다 연관 관계의 Fetch 모드를 유연하게 설정 할 수 있는 @EntityGraph 사용

 

 

마치며

 

이직 후 바빠서 포스팅을 못했는데 이제 시간을 내서 틈틈이 작성해보자!