Table of Contents
프록시
- 지연로딩 : 엔티티가 실제로 사용될 때까지 데이터베이스 조회를 지연하는 방법을 제공
- 프록시 : 지연 로딩 기능을 사용하려면 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체
프록시 기초
1
Member member = em.getReference(Member.class, "member1");
위와 같은 코드는 데이터베이스에서 바로 조회하지 않고 실제 엔티티 객체도 생성하지 않는다.
대신 데이터베이스 접근을 위임한 프록시 객체를 반환한다.
프록시의 특징
- 처음 사용할 때 한 번만 초기화된다.
- 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있다.
- 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시에 주의해서 사용해야 한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환
- 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다.
프록시와 식별자
엔티티를 프록시로 조회할 때 식별자 값을 파라미터로 전달하는데 프록시 객체는 이 식별자 값을 보관한다.
식별자 값을 조회하는 team.getId()를 호출해도 프록시를 초기화하지 않는다.
단 엔티티 접근 방식을 프로퍼티(@Access(AccessType.PROPERTY)로 설정한 경우에만 초기화하지 않는다.
엔티티 접근 방식을 필드(@Access(AccessType.FIELD)로 설정하면 JPA는 getId() 메소드가 Id만 조회하는 메소드인지 다른 필드까지 활용해서 어떤 일을 하는 메소드인지 알지 못하므로 프록시 객체를 초기화한다.
프록시는 다음 코드처럼 연관관계를 설정할 때 유용하게 사용할 수 있다.
1
2
3
Member member = em.find(Member.class, "member1");
Team team = em.getReference(Team.class, "team1");
member.setTeam(team);
참고로 연관관계를 설정할 때는 엔티티 접근 방식을 필드로 설정해도 프록시를 초기화하지 않는다.
프록시 확인
PersistenceUnitUtil.isLoaded(Object entity) 메소드를 사용하면 프록시 인스턴스의 초기화 여부를 확인할 수 있다.
- 초기화가 안 되었다면 false
- 초기화가 됐거나 프록시 인스턴스가 아니면 true
조회한 엔티티가 진짜 엔티티인지 프록시로 조회한 것인지 확인할려면 클래스명을 직접 출력해보면 된다.
클래스 명 뒤에 ..javaassit..라 되어 있다면 프록시이다. 하지만, 프록시를 생성하는 라이브러리에 따라 출력 결과는 달라질 수 있다.
즉시 로딩과 지연 로딩
즉시 로딩
즉시 로딩을 사용하면 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
FetchType.EAGER로 지정한다.
지연 로딩
연관된 엔티티를 실제 사용할 때 조회한다.
FetchType.LAZY로 지정한다.
JPA 기본 폐치 전략
- @ManyToOne, @OneToOne: 즉시 로딩
- @OneToMany, @ManyToMany: 지연 로딩
연관된 엔티티가 하나면 즉시 로딩, 컬렉션이면 지연 로딩
추천하는 방법은 모든 연관관계에 지연 로딩을 사용하는 것이다. 그리고 애플리케이션 개발이 어느 정도 완료단계에 왔을 때 실제 사용하는 상황을 보고 꼭 필요한 곳에만 즉시 로딩을 사용하도록 최적화하면 된다.
컬렉션에 즉시 로딩 사용 시 주의점
- 컬렉션을 하나 이상 즉시 로딩하는 것은 권장하지 않는다.
- 컬렉션 즉시 로딩은 항상 외부 조인을 사용한다.
- @ManyToOne, @OneToOne
- (optional = false): 내부 조인
- (optional = true): 외부 조인
- @OneToMany, @ManyToMany
- (optional = false): 외부 조인
- (optional = true): 외부 조인
영속성 전이: CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 된다.
JPA는 CASCADE 옵션으로 영속성 전이를 제공한다.
아래는 예제 코드
1
2
3
4
5
6
7
@Entity
public class Parent {
...
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> children = new ArrayList<Child>();
...
}
CASCADE의 종류
1
2
3
4
5
6
7
8
public enum CascadeType {
ALL, // 모두 적용
PERSIST, // 영속
MERGE, // 병합
REMOVE, // 삭제
REFRESH, // REFRESH
DETACH // DETACH
}
참고로 CascadeType.PERSIST, CascadeType.REMOVE는 em.persist(), em.remove()를 실행할 때 바로 전이가 발생하지 않고 플러시를 호출할 때 전이가 발생한다.
고아 객체
JPA는 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데 이것을 고아 객체 제거 라 한다.
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class Parent {
@Id @GeneratedValue
private Long id;
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<Child();
...
}
Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
위와 같이 고아 객체 제거 옵션을 키고 제거한 경우 데이터베이스에서도 고아 객체는 삭제된다.
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능 이다.
따라서 이 기능은 참조하는 곳이 하나일 때만 사용해야 한다.
삭제한 엔티티를 다른 곳에서도 참조한다면 문제가 발생할 수 있다.
이러한 이유로 @OneToOne, @OneToMany에서만 사용할 수 있다.
또한 부모를 제거할 경우, 자식은 고아가 된다.
그래서 자식도 같이 제거하게 된다.