전에 프로젝트를 진행하면서 프록시는 가짜 객체를 조회한다는 걸로 알고 있었는데 블로그 작성을 해보려 한다.
회원 엔티티, 팀 엔티티가 있고 두 가지 경우를 조회를 할때
// 회원과 팀 정보를 출력
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());
}
아래 코드의 경우 회원 엔티티만 사용하므로 em.find()로 회원을 조회할 때 연관된 팀 엔티티까지 함께 조회 해 두는것은 효율적이지 않다.
JPA는 이런 문제를 해결하려고 엔티티가 실제 사용될 때까지 DB 조회를 지연하는, 지연 로딩을 지원한다.
지연 로딩 기능을 사용하려면 실제 엔티티 객체 대신에 DB 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체라고 한다.
1. 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회한다.
2. 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청 하는데 초기화라고 한다
3. 영속성 컨텍스트는 데이터 베이스를 조회해해서 실제 엔티티 객체를 생성한다.
4. 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버변수에 보관한다.
5. 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.
Team team = em.getReference(Team.class, "team1"); //식별자 보관
team.getId(); //초기화 되지 않음
@ManyToOne(fetch = FetchType.EAGER)
@ManyToOne(fetch = FetchType.LAZY)
외래키가 NULL 값을 허용할 경우 팀에 소속하지 않은 회원과 팀을 내부 조인하면 팀은 물론이고 회원 데이터도 조회할 수 없다.
JPA는 이런 상황을 고려해 외부 조인(LEFT OUTER JOIN)을 사용한다.
하지만 외부 조인 보다 내부 조인이 성능과 최적화에 더 유리하다.
외래키에 NOT NULL 제약 조건을 설정하면 값이 있는 것을 보장한다. 따라서 이때는 내부 조인만 사용해도 된다.
@Entity
public class Member{
@ManyToOne(fetch = FetchType.EAGER)
@JoinColum(name = "TEAM_ID", nullabe = false)
private TEAM team;
}
nullable = false 대신에 @ManyToOne.optional = false 를 사용해도 내부 조인을 사용할 수 있다.
JPA 기본 페치 전략은 연관된 엔티티가 하나면 즉시 로딩, 컬렉션이면 지연 로딩 - 비용이 많이 들고, 자칫하면 너무 많은 데이터를 로딩 할수 있기 때문
추천하는 방법은 모든 연관관계에 지연로딩을 사용하는 것이다.
컬렉션을 하나 이상 즉시 로딩하는 것은 권장하지 않는다.
커렉션 즉시 로딩은 항상 외부 조인을 사용한다.
팀테이블에서 회원 테이블로 일대다 관계를 조인 할 경우 회원이 한명도 없는 팀을 내부조인하면 팀까지 조회되지 않는 문제를 만날 수 있다.
JPA는 일대다 관계를 즉시 로딩할 때 항상 외부조인을 사용한다.