김영한님의 JPA 강의를 보고 정리한 내용입니다.
지연 로딩의 경우 연관관계 매핑 어노테이션에 fetch = FetchType.LAZY 속성을 추가해서 선언해준다. 연관관계 어노테이션 중 *ToMany에 해당하는 어노테이션의 기본 값이다.
@OneToMany(fetch = FetchType.LAZY)지연 로딩을 사용하게 되면 객체를 DB가 아닌 프록시에서 가져온다. 프록시에서 객체를 가져올 경우 실제로 사용(초기화)되기 전까지 조회 쿼리를 사용하지 않는다.
예를 들어 Member와 Team 클래스가 있고, Member에 @ManyToOne(fetch = FetchType.LAZY)로 Team과 연관관계를 맺고 있다고 가정해보자.
@Entity
class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
...
}
지연 로딩의 경우 Member 객체를 저장하고, member를 조회하게 되면 member는 DB에서 조회를 하고, 조회된 member안의 Team 객체는 프록시로 가져온다.
그리고 Team을 실제로 사용할 때 DB에서 조회 후 가져오게 된다.
member 객체를 조회할 때 굳이 team 객체를 조회할 필요가 없으므로 DB에 쿼리가 한 번만 나가게 돼서 성능상의 이점을 얻는다.
하지만 지연 로딩 역시 후에 서술할 N+1의 문제가 있다
즉시 로딩의 경우 연관관계 매핑 어노테이션에 fetch = FetchType.EAGER 속성을 추가해서 선언해준다. 연관관계 어노테이션 중 *ToOne에 해당하는 어노테이션의 기본 값이다.
@ManyToOne(fetch = FetchType.EAGER)즉시 로딩을 사용하면 무조건 조회하는 시점에 필요한 값들이 모두 들어가 있어야 한다.
예를 들어 Member와 Team 클래스가 있고, Member에 @ManyToOne(fetch = FetchType.EAGER)로 Team과 연관관계를 맺고 있다고 가정해보자.
@Entity
class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "team_id")
private Team team;
...
}
즉시 로딩의 경우 Member 객체를 저장하고, member를 조회하게 되면 Team 객체의 사용 여부와 관계없이 조회 시점에 team이 무조건 들어가 있어야 하기 때문에 조회 시 두 엔티티를 조인해서 가져온다.
비즈니스 로직에서 두 객체가 동시에 사용되는 빈도가 높다면 즉시 로딩을 사용할 것 같다. 하지만 그것은 어디까지나 이론에서 적용되는 것이고, 실제 실무에선 무조건 지연 로딩을 사용한다고 한다.
N+1의 문제를 발생시킴em.createQuery("select m from Member m", Member.class).getResultList()SQL: select * from MemberMember 객체를 전부 다 가져오게 되는데 이때 Member 내부를 보니 Team에 대한 매핑이 즉시 로딩으로 되어있는 것을 확인하고 SQL을 한 번 더 실행SQL: select * from Team where ...Member의 개수만큼 나감Member의 개수 N에 최초 Member를 조회하는 쿼리까지 합한 N+1번 쿼리가 실행됨결론은 모든 연관관계에 지연 로딩을 사용하고, JPQL 패치 조인이나, 엔티티 그래프 기능을 사용하자.