티스토리 뷰

엔티티를 조회할 때, 연관된 데이터들이 항상 사용되는 것은 아니다. 예를들어 Question 엔티티가 아래와 같이 List<Answer>를 가지고 있다고 하자!

물론 질문 상세보기에서 해당 답변 목록을 보여줄때는 List<Answer>는 필요하다. 하지만 질문 상세 보기 이전에 질문들의 목록을 나타내는 게시판에서

Answer의 갯수만 필요하다고 한다면, 굳이 List<Answer>의 목록이 필요없다. 오히려 List<Answr>를 모두 가져와야 한다는 것에서 굉장히 효율적이지 못한다.

이럴때는 Answer의 갯수를 관리하는 필드가 있고, Answer의 내용이 필요할때만 Answer를 가져오고 그러지 않을 때는 Question의 필드값만 불러오면

가장 베스트이지 않을까? 위와 같이 엔티티가 실제 사용될 때까지 데이터베이스의 데이터를 조회하는 것을 지연하는 방법을 지연로딩이라고 한다.

오늘은 지연로딩에 대해서 알아보려고 한다.


         
    @OneToMany(mappedBy = "question", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @Where(clause = "deleted = false")
    @OrderBy("id ASC")
    private List answers;


1. 프록시


지연로딩을 사용하려면 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있도록 지원하는 가짜 객체가 필요한데 이를 프록시라고 한다.

프록시 클래스는 실제 클래스를 상속받아서 만들어지므로, 실제 클래스와 겉 모양이 유사하다. 그리고 프록시 클래스는 Entity target 이라는 인스턴스를 가지고 있는데 이 필드는 실제 객체에 대한 참조를 보관하고 있다. 그리고 프록시 객체의 메소드를 호출하면 프록시 객체의 메소드가 호출되는 것이 아닌, 참조하고 있는 객체의 메소드가 호출된다. 프록시 객체는 아래와 같은 순서로 초기화가 되어 사용된다.


(1) 프록시 객체에 메소드를 호출하면, 초기화가 진행된다.

     영속성 컨텍스트에 실제 엔티티가 생성되어 있지 않으면, 데이터베이스 접근하여 데이터를 가져와 실제 엔티티를 생성한다.

 (2) 프록시 Entity target 인스턴스에 생성된 인스턴스의 참조를 할당한다.

 (3) 프록시 객체는 실제 객체(즉, target의 참조변수)의 메소드를 호출한다.


프록시 객체는 생성된 실제 엔티티를 상속받은 객체이므로 타입 체크 시에 주의해야 한다. 해당 객체가 프록시 객체인지, 실제 객체인지 확인을 하려면 

이름을 보면 알 수 있다. obj.getClass().getName()을 했을 때, 객체의 이름에 ..javassist.. 가 있다면 이 객체는 프록시 객체이다!

 - PersistenceUnitUtil.isLoaded(Object entity) 메소드를 사용하면 프록시 인스턴스의 초기화 여부 확인 가능


프록시 객체를 초기화할 때, 실제 객체가 영속성 컨텍스트에 존재한다면, 데이터베이스에 접근하지 않고, 프록시가 아닌 실제 객체를 반환한다.

결국은 영속성 컨텍스트에 있다면 지연로딩을 할 필요가 없다. ( em.getEeference() 호출하면 프록시 객체가 아닌, 실제 객체 반환 )


지연로딩은 영속성 컨텍스트의 도움을 받아야 가능하기 때문에 오직, 영속 상태의 엔티티에 대해서만 허용. 준영속, 비영속은 사용이 불가!

준영속 상태의 프록시를 초기화하면 예외 발생한다.



2. 즉시로딩, 지연로딩


결론부터 말하자면, 즉시로딩은 엔티티를 조회할 때, 연관된 엔티티도 함께 조회한다. (Question을 조회할 때, List<Answer> 도 조회)

그리고 지연로딩은 연관된 엔티티를 실제 사용할 때 조회한다. (Quesion을 조회할 때, List<Answer>도 사용한다면 그때만 조회)


즉시로딩은 데이터를 불러올 때, 조인쿼리를 이용해서 연관된 데이터를 모두 조회하기 때문에 연관된 데이터가 항상 같이 사용한다면 지연로딩보다 성능이 좋다. 그렇다면 즉시로딩일 때, inner join, outter join 중 어떤 쿼리문을 사용할까? 이것은 참조키가 null을 허용하는지 허용하지 않는지에 따라 다르다.


 - @JoinColumn(nullable = true) : 외부조인 (합집합)

 - @JoinColumn(nullable = false) : 내부조인 (교집합)


예를들어, ManytoOne 관계 (Qustion(n) : Writer(1)) 에서 writer가 null이 허용되었을 때, 내부조인을 사용한다면, writer가 없는 글은 조인을 통해 결과를 확인할 수 없다. 왜냐면 내부조인을 쉽게 말해서 두 연관된 테이블에서 공통적인 부분만 불러오는 것이기 때문에 writer가 null이라면 해당 Question은 불러올 수 없다.

그러나 null이 허용되지 않으면, 두 연관된 테이블은 항상 공통적인 부분만 있기 때문에 내부조인 가능하다. 그리고 성능면에서 내부조인이 외부조인보다 좋다.


지연로딩은 연관된 엔티티를 프록시로 조회하고, 프록시를 실제 사용할 때 초기화하면서 데이터베이스를 조회한다. 그러나 만약에 조회대상이 영속성 컨텍스트에 존재한다면, 프록시 객체가 아닌 실제 객체를 사용한다. 그리고 추가적으로 컬렉션은 지연로딩을 지원하는 것이 유리하다. 


처음부터 모든 엔티티를 영속성 컨텍스트에올려두는 것은 현실적이지 않고, 성능도 좋지 않다.

그리고 항상 지연로딩을 지원하도록 설정하는 것도 좋지 않다. 왜냐면 이왕 모든 데이터를 가져온다면 조인 쿼리를 사용하는 것이 성능이 더 좋다.

결론적으로 지연로딩을 할지, 즉시로딩을 할지는 상황에 따라 다르다!


3. JPA 기본 패치 전략


 - ManyToOne, OneToOne : 즉시로딩

 - OneToMany, ManyToMany : 지연로딩


연관된 엔티티가 하나의 객체라면 즉시로딩을 기본적으로 지원하고, 컬렉션이라고 한다면 지연로딩을 지원한다. 왜냐면, 객체가 하나라면 사용유무와 

상관없이 바로 가져온다고 해도 부담이 없다. 어쩌면 오히려 조인쿼리를 사용해서 데이터를 가져오기 때문에 더 성능이 좋을 수 있다. 

하지만, 연관된 엔티티가 10만개의 데이터를 가지고 있는 컬렉션이고, 사용하지 않는다고 하면, 굳이 가져와야할 필요가 있을까? 정말 필요한 순간에만 

가져오는 것이 성능면에서 좋지 않을까?

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함