•
이 장에서 다룰 내용은 다음과 같다.
◦
프록시와 즉시로딩, 지연로딩
▪
객체는 객체 그래프로 연관된 객체들을 탐색할 수 있다. 그러나 데이터베이스에 저장되어 있으므로 연관된 객체를 마음껏 제약없이 탐색하기는 어렵다.
▪
JPA 구현체들은 이 문제를 해결하기 위해 프록시라는 기술을 사용한다. 프록시를 사용하면 연관된 객체를 처음부터 데이터베이스에서 조회해두지 않고 실제 사용하는 시점에 조회할 수 있다.
▪
하지만 자주 사용하는 객체들은 조인을 사용해서 함께 조회하는 것이 효과적이다.
▪
JPA는 즉시 로딩과 지연 로딩이라는 방법으로 둘 모두 지원한다.
◦
영속성 전이와 고아 객체
▪
JPA는 연관된 객체를 함께 저장하거나 함께 삭제할 수 있는 영속성 전이와 고아 객체 제거라는 편리한 기능을 제공한다.
1. 프록시
•
JPA는 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공하는데, 이를 지연 로딩이라 한다.
•
이때 실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데, 이것을 프록시 객체라 한다.
1. 프록시 기초
•
엔티티를 실제 사용하는 시점까지 데이터베이스 조회를 미루고 싶다면 EntityManager.getReference() 메소드를 사용하면 된다.
◦
만약 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 프록시가 아닌 실제 엔티티가 반환된다.
•
프록시의 특징
◦
프록시 클래스는 실제 클래스를 상속 받아서 만들어지므로 실제 클래스와 겉 모양은 같다.
◦
프록시 객체는 실제 객체에 대한 참조를 보관하며 프록시 객체의 메소드를 호출하면 프록시 객체는 내부적으로 참조하는 실제 객체의 메소드를 호출한다.
◦
프록시 객체는 처음 사용 시, 한 번만 초기화되며 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다.
◦
초기화는 영속성 컨텍스트의 도움을 받아야 가능하므로 준영속 상태의 프록시를 초기화하면 문제가 발생한다.
•
프록시 객체의 초기화
◦
프록시 객체는 member.getName()처럼 실제 사용될 때, 데이터베이스를 조회해서 실제 엔티티 객체를 생성하는데 이것을 프록시 초기화라 한다.
2. 프록시와 식별자
•
엔티티를 프록시로 조회 시, 식별자 값을 파라미터로 전달하는데, 프록시 객체는 이 식별자 값을 보관한다.
◦
프록시 객체는 식별자 값을 가지고 있기 때문에 엔티티 접근 방식이 프로퍼티인 경우, 식별자 값을 조회하는 메소드가 호출되어도 프록시를 초기화하지 않는다.
3. 프록시 확인
•
JPA가 제공하는 isLoaded(Object entity) 메소드를 사용하면 프록시 인스턴스의 초기화 여부를 확인할 수 있다.
2. 즉시 로딩과 지연 로딩
•
프록시 객체는 주로 연관 데이터를 지연 로딩하는 경우 사용한다.
•
JPA는 개발자가 연관된 엔티티의 조회 시점을 결정할 수 있도록 다음 두 가지 방법을 제공한다.
◦
즉시 로딩: 엔티티 조회 시, 연관된 엔티티 함께 조회
◦
지연 로딩: 연관된 데이터를 실제 사용할 때 조회
1. 즉시 로딩
•
즉시 로딩을 사용하려면 @ManyToOne의 fetch 속성을 FetchType.EAGER로 지정한다.
•
대부분의 JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다.
2. 지연 로딩
•
지연 로딩을 사용하려면 @ManyToOne의 fetch 속성을 FetchType.Lazy로 지정한다.
3. 즉시 로딩, 지연 로딩 정리
•
지연 로딩과 즉시 로딩 중 어느 하나가 절대적으로 낫다고 할 수 없다. 따라서 상황에 따라 적절한 선택을 하자.
◦
지연 로딩은 연관된 엔티티를 프록시로 조회하며 프록시를 사용할 때 초기화하면서 데이터베이스를 조회한다.
◦
즉시 로딩은 연관된 엔티티를 즉시 조회하며 하이버네이트는 가급적이면 SQL 조인을 사용해 쿼리를 최적화한다.
3. 지연 로딩 활용
1. 프록시와 컬렉션 래퍼
•
하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티의 컬렉션 타입을 추적하고 관리하기 위해 하이버네이트가 제공하는 내장 컬렉션으로 변경하는데 이를 컬렉션 래퍼라한다.
•
엔티티를 지연 로딩하면 프록시 객체를 사용하지만 컬렉션은 컬렉션 래퍼라 지연 로딩을 처리해준다.
•
컬렉션의 경우, 컬렉션 자체를 참조하는 경우엔 초기화되지 않으며 컬렉션 내부의 객체를 참조할 때 초기화된다.
2. JPA 기본 페치 전략
•
fetch 속성의 기본 설정값은 다음과 같다.
◦
@ManyToOne, @OneToOne: 즉시 로딩
◦
@OneToMany, @ManyToMany: 지연 로딩
•
연관된 객체가 하나면 즉시 로딩을, 컬렉션이면 지연 로딩을 사용하는데, 컬렉션을 로딩하는 것은 비용이 많이 들고 잘못하면 너무 많은 데이터를 로딩할 수 있기 때문이다.
•
추천하는 방법은 모든 연관관게에 지연 로딩을 사용하는 것이다. 어플리케이션 개발이 어느 정도 완료단계가 되었을 때 실제 사용 시 데이터를 확인하고 꼭 필요한 곳에만 즉시 로딩을 적용하도록 한다.
3. 컬렉션에 FetchType.EAGER 사용 시 주의점
•
컬렉션을 하나 이상 즉시 로딩하는 것은 권장하지 않는다.
◦
컬렉션과 조인한다는 것은 일대다 조인이다. 일대다 조인은 결과 데이터가 다 쪽에 있는만큼 증가하게 된다.
◦
문제는 서로 다른 컬렉션을 2개 이상 조인할 때 발생한다. 각각 데이터 갯수가 N, M이면 N * M이 결과 데이터가 되면서 성능에 문제가 생길 수 있다.
◦
따라서 2개 이상의 컬렉션을 즉시 로딩으로 설정하는 것은 권장하지 않는다.
•
컬렉션 즉시 로딩은 항상 외부 조인을 사용한다.
◦
내부 조인을 사용하는 경우 not null 제약 조건을 고려하지 않아 데이터에 문제가 생길 수 있으므로 항상 외부 조인을 사용해 드라이빙 테이블의 데이터는 항상 조회됨을 보장한다.
4. 영속성 전이: CASCADE
•
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 된다. JPA는 CASCADE 옵션으로 영속성 전이를 제공한다.
•
JPA에서 엔티티를 저장할 때, 연관된 모든 엔티티는 영속 상태여야 한다. 영속성 전이를 사용하면 부모만 영속 상태로 만들어도 연관된 자식들도 한 번에 영속 상태로 만들 수 있다.
1. 영속성 전이: 저장
@Entity
public class Parent {
@OneToMany(mappedBy = "parent', cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<Child>();
}
Java
복사
•
부모를 영속화할 때 연관된 자식들도 함께 영속화하는 옵션을 추가했다. 이 옵션을 적용하면 다음과 같이 부모와 자식 엔티티를 한 번에 영속화할 수 있다.
private static void saveWithCascade(EntityManger em) {
Child child1 = new Child();
Child child2 = new Child();
Parent parent = new Parent();
child1.setParent(parent);
child2.setParent(parent);
parent.getChildren().add(child1);
parent.getChildren().add(child2);
em.persist(parent);
}
Java
복사
•
부모만 영속화해도 자식 엔티티까지 함께 영속화해서 저장한다.
•
영속성 전이는 연관관계 매핑과는 아무 관련이 없다. 단지 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공할 뿐이다.
2. 영속성 전이: 삭제
•
영속성 전이는 엔티티를 삭제할 때도 사용할 수 있다.
•
CascadeType.REMOVE로 설정하고 부모 엔티티만 삭제하면 연관된 자식 엔티티도 함께 삭제된다.
3. CASCADE의 종류
•
ALL(모두 적용), PERSIST(영속), MERGE(병합), REMOVE(삭제), REFRESH, DETACH와 같은 옵션이 존재하며 여러 속성을 같이 사용할 수 있다.
5. 고아 객체
•
JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하며 이를 고아 객체 제거라 한다.
•
부모 엔티티의 컬렉션에 매핑 시 속성으로 orphanRemoval = true을 사용하면 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제된다.
•
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능으로 참조하는 곳이 하나일 때만 사용하는 것이 좋다.
6. 영속성 전이 + 고아 객체, 생명주기
•
CascadeType.ALL과 orphanRemoval을 동시에 사용하면 어떻게 될까?
•
두 옵션을 모두 활성화하면 부모 엔티티를 통해 자식의 생명주기를 관리할 수 있다. 자식을 저장하려면 부모에 등록하고 삭제하려면 부모에서 제거한다.
7. 정리
•
JPA 구현체들은 객체 그래프를 마음껏 탐색할 수 있도록 지원하는데 이때 프록시 기술을 사용한다.
•
객체를 조회할 때 연관된 객체를 즉시 로딩하는 방법을 즉시 로딩, 연관된 객체를 지연해서 로딩하는 방법을 지연 로딩이라 한다.
•
객체를 저장하거나 삭제할 때 연관된 객체로 함께 저장하거나 삭제할 수 있는데 이것을 영속성 전이라고 한다.
•
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하려면 고아 객체 제거 기능을 활성화하면 된다.