Springboot

JPA/지연로딩과 즉시로딩 개념과 권장 사용법 정리, 그리고 OneToOne의 지연로딩

25G 2022. 10. 21. 18:17

지연로딩(Lazy)? 즉시로딩(Eager)?

entity의 연관관계가 매핑된 상황을 가정하고 지연로딩과 즉시로딩을 간단하게 설명하면 다음과같습니다.

 

지연로딩 : 연관된 엔티티를 프록시로 조회한다. 프록시를 실제사용할때 (예를들어 getter호출) 초기화하면서 데이터 베이스를 조회합니다.

즉시로딩 : 연관된 엔티티를 즉시 조회합니다. 하이버네이트는 가능하면 SQL JOIN을 통해서 한번에 조회합니다.

 

그렇다면 어느것이 더 좋은것을까?

처음부터 연관된 엔티티를 모두 영속성 컨텍스트에 올려두는것은 현실적이지 않습니다. 산에가는데 꼭 모든 장비가 있어야하는것은 아닙니다. 때로는 물통한통만 있어도 되는것과 같습니다.

그럼 지연로딩이라고 무조건 좋은것일까? 한다면 최적화 관점에서보면 꼭 좋지만은 않습니다. 대부분의 애플리케이션 로직에서 연관관계의 객체그래프를 함께 사용한다면 이또한 IO가 많아지니 좋은 방법일수 없습니다. 

제가 생각하기에 가장 좋은것은 등산을 할때 꼭 필요한것들은 EAGER로 항상 들고다니는 체크리스트에 들어가고 경우에 따라서 높은 산을 올라갈땐 지팡이를 챙긴다던지 겨울엔 아이젠을 챙긴다던지 하는 것과 같이 개발자가 비지니스를 잘 파악하여 상황에 맞게 Entity 설계를 하는것이 아닐까 생각합니다.

 

하지만! 관계가 복잡해지고 얽혀있을수록 즉시로딩으로 설정된 연관관계들 때문에 개발자가 전혀 의도치않은 쿼리가 보내지게 됩니다. 더불에 1+N문제를 가지고 있습니다. 그렇기 때문에 프로젝트가 커지고 관계가 복잡해질수록 지연로딩 (LAZY)를 선호합니다.

 

컬렉션에 EAGER 사용시 주의점

- 컬렉션을 하나 이상 즉시 로딩하는것은 권장하지 않습니다. 컬렉션과 조인한다는 것은 데이터베이스 테이블의 입장에서 보면 일대다 조인입니다. 일대다 조인은 결과 데이터가 다 쪽에 있는 수만큼 증가하게 됩니다. 문제는 서로 다른 컬렉션을 2개 이상 조인할때 발생하는데 예를 들어 A 테이블을 N,M 두 테이블과 일대다 조인을 하면 SQL실행결과가 N곱하기M이 됩면서 너무 많은 데이터를 반환할 수 있고 결과적으로 애플리케이션 성능이 저하될 수 있습니다. JPA는 이렇게 조회된 결과를 메모리에서 필터링해서 반환합니다. 그렇기때문에 권장하지 않습니다.

- 컬렉션의 즉시로딩은 무적권 외부조인 outer join을 사용합니다. 간단히 생각해보면 하이버네이트입장에서는 이 데이터가 있을지 없을지 알수 없는데 inner join을 하게되면 데이터를 들고 올 수 없게 되기 때문이지요.

 

그렇기때문에 연관관계설정시 중요하게 설정해야할 것이 optional 설정입니다.

@ManyToOne, @OneToOne

- optional = false : 내부조인

- optional = true : 외부조인

@OneToMany, @ManyToMany

- optional = false : 외부조인

- optional = true : 외부조인

 

김영한님의 JPA책에서 추천하는 방법은 모든 연관관계를 lazy로 설정한다음 완성단계에서 쿼리로그를보며 최소한의 부분만 EAGER로 설정하여 운영하는것입니다.

 

OneToOne에서 지연로딩

OneToOne의 N+1 문제를 해결하기위해서는 optional=false로 설정하여 엔티티가 무적권 있음을 보장하여 하이버네이트가 프록시객체를 당당하게 넣어줄수있게 설정을 해주는것입니다.

하지만 OneToOne자체를 권장하지않으며 OneToOne 연관관계를 맺는상황을 최대한 피해서 설계하는것이 좋은 설계가 될 수 있습니다. 불가피한 상황에서만 다음과 같이 설정하는것입니다.

@OneToOne(fetch = FetchType.LAZY, optional = false) // not null