1. 개요
김영한 님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 을 들으면서 정리하는 포스팅입니다.
https://www.inflearn.com/course/ORM-JPA-Basic
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com
2. 즉시로딩(Eager Loading)과 지연로딩(Lazy Loading)
JPA에서 Entity 로딩 전략으로 즉시로딩, 지연로딩 2가지 방식이 있다.
각각 전략이 무엇인지 그리고 어느 상황에 적합하게 사용하는지 알아보자.
a. 즉시로딩
- 즉시로딩은 엔티티를 조회할 때 데이터를 즉시 가져오는 로딩 전략이다.
- em.find() 메서드를 통해 엔티티를 조회하면, 즉시 쿼리를 실행하여 로드한다.
- 메서드 호출과 동시에 쿼리를 실행하는 일반적인 방식이다.
즉시로딩 예제
System.out.println("===== 호출 전 =====");
Member findMember = em.find(Member.class, member1.getId()); // 실제 엔티티 조회(즉시 조회)
System.out.println("findMember.getClass() = " + findMember.getClass());
System.out.println("===== 호출 후 =====");
System.out.println("findMember.username = " + findMember.getUsername());
즉시로딩 실행결과
- em.find() 호출시 select 쿼리를 즉시 실행하여 엔티티를 조회한다.
b. 지연로딩
- 지연로딩은 엔티티를 조회할 때 데이터를 즉시 가져오지 않고, 필요할 때 로드하는 전략이다.
- 초기 조회 시에는 데이터 대신 프록시 객체를 생성하고, 필요 시점에 select 쿼리를 실행하여 로드한다.
- 데이터가 필요해지는 시점(예 getter 호출)에 데이터베이스 쿼리가 실행된다.
지연로딩 예제
System.out.println("===== 호출 전 =====");
Member findMember = em.getReference(Member.class, member1.getId()); // 지연로딩
System.out.println("findMember.getClass() = " + findMember.getClass());
System.out.println("===== 호출 후 =====");
System.out.println("findMember.username = " + findMember.getUsername());
지연로딩 실행결과
- em.getReference() 호출 시 데이터베이스 쿼리가 실행되지 않고, getUsername()을 호출할 때 쿼리가 실행된다.
- Member 타입 출력 결과를 보면 Member 타입이 아니다.
- 지연로딩 시 프록시 객체를 반환하기 때문이다.
엔티티 프록시 객체란?
- jpa에서 지연로딩을 사용하면 select 쿼리를 미루기 위해 가짜 객체(proxy)를 반환한다.
- 프록시 객체는 실제 엔티티 대신 반환되는 가짜 객체로 실제 엔티티 클래스를 상속 받아서 만들어진다.
- 프록시 객체는 실제 객체의 reference(target)를 보관한다.
- 실제 클래스와 겉모양은 같다.(사용하는 입장에서 구분하지 않아도 됨.)
지연로딩 동작과정
Member findMember = em.getReference(Member.class, member1.getId()); // 지연로딩
System.out.println("findMember.username = " + findMember.getUsername());
- getUsername() 을 호출한다.(실제 데이터가 필요한 시점)
- 영속성 컨텍스트에 초기화 요청을 보낸다.
- member가 영속성 컨텍스트에 존재하지 않으면 DB에서 조회한다.
- DB 조회 결과를 가지고 실제 엔티티를 생성한다.
- 프록시 객체에서 reference를 가지고 있는 Member 엔티티의 getUsername()을 호출한다.
c. 지연로딩과 즉시로딩 사용
위 예제와 같이 단일 엔티티를 조회할 때 지연로딩 전략은 잘 사용하지 않는다.(결국 엔티티를 쓰려고 호출하기 때문에 지연로딩의 장점이 사라짐.) 그러므로 단일 엔티티의 경우 즉시 로딩이 일반적이다.
지연로딩은 연관관계가 맺어진 엔티티를 조회할 때 유용하다. 자세한 내용은 아래에서 다뤄보자.
3. 연관관계 Entity의 로딩 전략
연관관계가 맺어진 대상을 즉시로딩할지 지연로딩할지 설정할 수 있다.
즉시로딩으로 설정된 대상 엔티티는 주 엔티티를 조회할 때 join 쿼리를 사용하여 즉시 로드하게 된다.
반대로, 지연로딩으로 설정된 대상 엔티티는 조회를 미루다가 실제 사용시점에 DB에서 조회한다.
a. 즉시로딩(FetchType.EAGER)
- 연관된 엔티티의 featch 옵션을 EAGER로 설정하면 대상 엔티티를 즉시 로딩한다.
- 연관된 엔티티를 JOIN 쿼리로 즉시 조회한다.
- @ManyToOne, @OneToOne은 디폴트가 즉시로딩이다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "username")
private String username;
@ManyToOne(fetch = FetchType.EAGER) // 즉시로딩 전략
@JoinColumn(name = "team_id")
private Team team;
// getter, setter
}
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
// getter, setter
}
System.out.println("===== 호출 전 =====");
Member findMember = em.find(Member.class, member1.getId()); // 실제 엔티티 조회(즉시 조회)
System.out.println("===== 호출 후 =====");
System.out.println("findMember.username = " + findMember.getUsername());
즉시로딩 결과
- Member(주 엔티티)를 조회할 때 join 쿼리로 Team을 함께 조회한다.
b. 지연로딩(FetchType.LAZY)
- 연관된 엔티티의 featch 옵션을 LAZY로 설정하면 대상 엔티티 지연로딩 전략을 사용한다.
- 연관관계 대상을 즉시 조회하지 않고 미루다가 사용 시점에 DB에서 조회한다.
- @OneToMany, @ManyToMany는 디폴트가 지연로딩이다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "username")
private String username;
@ManyToOne(fetch = FetchType.LAZY) // 지연로딩 전략
@JoinColumn(name = "team_id")
private Team team;
// getter, setter
}
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
// getter, setter
}
System.out.println("===== 호출 전 =====");
Member findMember = em.find(Member.class, member1.getId()); // member 조회
System.out.println("===== 호출 후 =====");
System.out.println("findMember.username = " + findMember.getUsername()); // team 조회(지연로딩)
지연로딩 결과
- Member를 조회할 때 Team을 조회하지 않고, getTeam()(실제사용시점)할 때 Team을 조회한다.
C. 즉시로딩, 지연로딩 선택 기준
이론적으로는 연관된 엔티티의 데이터를 자주 사용하는 경우에는 즉시 로딩을, 자주 사용하지 않는 경우에는 지연로딩을 사용하는 것이 좋다. 왜냐하면 연관된 엔티티를 사용하는 경우라면 한번에 쿼리하는 것이 효율적이기 때문이다.
하지만 실무에서는 즉시로딩을 거의 사용하지 않는다. 실무에서는 더욱 복잡한 엔티티 구조로 가진다.
그로 인해, 많은 join 쿼리 발생, 예상치 못한 쿼리 발생(예: N+1 문제), 메모리에 부하가 발생할 수 있기 때문이다.
'Programming > Java' 카테고리의 다른 글
[JPA] 값 타입 정리(기본값, 임베디드, 컬렉션) (0) | 2025.01.02 |
---|---|
[JPA] CASECADE(영속성 전이) 가 무엇인가? (0) | 2024.12.23 |
[JPA] JPA에서 DB 슈퍼-서브 타입 구현하는 방법 (0) | 2024.12.20 |
[JPA] Entity 연관 관계 매핑(단방향/양방향, 연관관계주인, 다중성) (0) | 2024.12.13 |
[JPA] JPA에서 PK를 다루는 방법(@Id, @GeneratedValue) (0) | 2024.12.12 |