JPA를 사용하면 객체는 연관관계가 있는 다른 객체를 필드로 가져 참조하는 방식으로 관계를 형성한다.
특징
엔티티를 프록시로 조회할 때 PK 값을 파라미터로 전달하는데 프록시 객체는 이 식별자 값을 보관한다.
School school = em.getReference(School.class, "School1");
school.getId(); //초기화되지 않음
이미 PK를 가지고 있었기 때문에 프록시 객체는 실제 객체로 초기화되지 않는다.
🚨단 엔티티 접근 방식을 프로퍼티 (@Access(AccessType.PROPERTY))
로 설정한 경우에만 초기화하지 않는다.
(@Access(AccessType.FIELD))
로 설정하면 JPA는 getId()
메소드가 id 만 조회하는 메소드인지 다른 필드까지 활용하는 것인지 모르기 때문에 프록시 객체를 초기화한다.
프록시는 다음 코드처럼 연관관계를 설정할 때 유용하다.
Member member = em.find(Member.class, "member1");
Team team = em.getReference(Team.class, "team1"); // SQL을 실행하지 않음
member.setTeam(team);
연관관계를 설정할 때는 식별자 값만 사용하기 때문에 프록시를 사용하면 데이터베이스 접근 횟수를 줄일 수 있다.이러한 경우에는 엔티티 접근 방식을 필드로 설정해도 프록시를 초기화하지 않는다.
프록시 객체는 주로 연관된 엔티티를 지연 로딩할 때 사용한다.
find()
했는데find()
하는 쿼리를 날린다면 추가 비용이 발생해서 아쉽기 때문에 이를 방지할 수 있다.find()
해서 가져오는 시점에 Team 객체를 가져오는 쿼리를즉시 로딩(EAGER LOADING)을 사용하려면 @ManyToOne
의 fetch 속성을 FetchType.EAGER 로 지정하여 사용할 수 있다.
@Entity
public class Member {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
위 예제 코드와 같이 코드를 작성하고 아래와 같이 get 요청을 해보자
Member member = em.find(Member.class, "member1");
Team team = member.getTeam();
이 코드는 EAGER 설정이 되어있기 때문에 회원을 조회하는 순간 팀도 함께 조회한다.
이때 회원과 팀 두 테이블을 조회해야 하기 때문에 쿼리를 2번 실행할 것 같지만, 대부분의 JPA 구현체는
즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용하기 때문에 여기서는 회원과 팀을 조인해서
쿼리 한번으로 두 엔티티르 모두 조회한다.
이후 member.getTeam()
을 호출하면 이미 로딩된 Team1 엔티티를 반환한다.
지연 로딩을 사용하려면 FetchType.LAZY 로 지정한다.
예를 들어서 주문이라는 클래스는 두 개의 컬렉션을 가지고 있다고 가정하자.
그럼 주문 객체를 DB에서 가져올 때 (조회할 때) 항상 A 컬렉션의 N개와B 컬렉션의 M 개
를 모두 가져오기 때문에 N M 개를 가져와야 한다.
하지만 이 문제를 LAZY 설정으로 하면 해결할 수 있을까? N M 개의 조회를 해야 한다는 사실은
바뀌지 않을 것이다. 하지만 Order 클래스의 컬렉션 정보가 필요한 게 아닌 순수 Order 객체의 필
드 값이 필요하다면 N * M 개를 조회하지 않기에 조금은 효율적이지 않을까 싶다.
같은 접근으로 외부조인 문제도 LAZY조건도 외부조인을 해야하는 것을 피할 수 없지만
필요할 때만 하는 방식으로 성능적인 면에서 더 유연하고 효율적이다.
N * M 을 해결하기 위해서는 각 개별 쿼리를 만들어주는 것으로 해결 가능할 것 같다.
참고
1. 인프런 JPA 기본편 - 김영한
2. 자바 ORM 표준 JPA 프로그래밍 - 김영한