// 회원과 팀 정보를 출력하는 예제
public void printUserAndTeam(String memberId) {
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름 : " + member.getUsername());
System.out.println("소속팀 : " + team.getName());
}
// 회원 정보만 출력하는 예제
public void printUser(String memberId) {
Member member = em.find(Member.class, memberId);
System.out.println("회원 이름 : " + member.getUsername());
}
위의 예제를 보면, printUserAndTeam( ) 메서드는 회원과 회원과 연관된 팀의 이름을 모두 출력한다.
반면에 printUser( ) 메서드는 회원의 이름만 출력한다.
printUser( ) 메서드는 회원 엔티티만 사용하는데, em.find( )를 사용해 회원과 연관된 팀 엔티티까지
조회하는 것은 효율적이지 못하다.
💡 JPA는 이런 문제를 해결하기 위해, 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 지연로딩 기능을 제공한다.
💡 JPA 표준 명세에는 준영속 상태의 엔티티를 초기화할 때 어떤 일이 발생할지 정의되어 있지 않다.
// 프록시 조회 예제
Team team = EntityManger.getReference(Team.class, "team1"); // 식별자 보관
team.getId(); // 초기화 하지 않는다.
// 프록시 사용 예제
Member member = EntityManager.find(Member.class, "member1");
Team team = EntityManager.getReference(Team.class, "team1"); // SQL을 실행하지 않는다.
member.setTeam(team);
// 프록시 초기화 여부 확인
boolean isLoad = EntityManager.getEntityManagerFactory()
.getPersistenceUnitUtil().isLoaded(entity);
// 프록시 초기화 여부 확인 다른 방법
boolean isLoad = EntityManagerFactory.getPersistenceUnitUtil().isLoaded(entity);
// 조회한 엔티티 프록시 객체 여부 확인(클래스 명 출력)
System.out.println("memberProxy = " + member.getClass().getName());
// 결과 : memberProxy = jpabook.domain.Member_$$_javassit_0
💡클래스 이름 출력결과는 프록시를 생성하는 라이브러리에 따라 출력 결과가 달라질 수 있다.
// 프록시 강제 초기화 예제
org.hibernate.Hibernate.initalize(order.getMember()); // 프록시 초기화
💡JPA 표준에는 프록시 강제 초기화 메서드가 없다. 강제로 초기화 하려면 프록시의 메서드를 호출하면 된다.
JPA는 개발자가 연관된 엔티티의 조회 시점을 선택할 수 있다.
// 즉시로딩 예제
@Entity
public class Member{
// ...
@ManyToOne(fetch = FetchType.EAGER)
@JoinCoulmn(name = "TEAM_ID")
private Team team;
// ...
}
Member member em.find(Member.class, "member1");
Team team = member.getTeam(); // 객체 그래프 탐색
즉시 로딩 실행 SQL에서 JPA는 내부조인(INNER JOIN)이 아닌 외부조인을 사용했다.
외래키가 NULL 값을 허용할 경우, 예를 들어 회원과 팀의 경우라면 팀에 소속되지 않은 회원이 있을 가능성이
있기 때문에 내부조인을 사용하면 팀은 물론이고 회원 데이터도 조회할 수 없다
조인을 성능으로 따지자면, 외부 조인보다 내부 조인이 성능과 최적화면에서는 훨씬 유리하다.
내부 조인을 사용하려면 외래키에 NOT NULL 제약조건을 줘서 값이 있다는 것을 보장하면 된다.
JPA에게 이런 사실을 알려주려면 한가지 설정만 해주면 된다.
// NOT NULL 제약조건 설정 예제
@Entity
public class Member {
// ...
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID", nullable = false)
private Team team;
// ...
}
💡nullable = false 대신 optional = false 를 사용해도 내부 조인을 사용할 수 있다.
// 지연 로딩 설정 예제
@Entity
public class Member{
// ...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
// ...
}
// 지연 로딩 실행 예제
Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); // 객체 그래프 탐색
team.getName(); // 팀 객체 실제 사용
💡 영속성 컨텍스트에 이미 찾는 엔티티가 있을경우 실제 객체를 반환한다.
// 컬렉션 래퍼 사용 예제
Member member = em.find(Member.class, "member1");
List<Order> orders = member.getOrders();
System.out.println("orders = " + orders.getClass().getName());
// 출력 결과 : orders = org.hibernate.collection.internal.PersistentBag
fetch 속성의 기본 설정 값
💡 모든 연관관계에 지연 로딩을 사용할 것을 추천한다.
컬렉션을 하나 이상 즉시 로딩하는 것은 권장하지 않는다.
💡 예를 들어, A 테이블을 N,M 두 테이블과 일대다 조인하면 SQL 실행 결과는 N * M이 된다. 너무 많은 반환하고, 애플리케이션 성능이 저하될 수 있다.
컬렉션 즉시 로딩은 항상 외부 조인을 사용한다.
💡 예를 들어, 회원 테이블과 팀 테이블을 조인할 경우, 회원 테이블의 외래 키에 not null 제약조건을 주면 모든 회원은 팀에 소속되므로 항상 내부조인 사용이 가능하다.
반면에, 팀 테이블에서 회원 테이블로 일대다 관계를 조인할 경우 회원이 한명도 없는 팀을 내부 조인하면 팀까지 조회되지 않는 문제를 만날 수 있다.
JPA는 일대다 관계를 즉시 로딩할 때 항상 외부조인을 사용한다.
오우