프록시
엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 지연 로딩이라 한다.
지연 로딩 기능을 사용할 때 실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체라고 한다.
프록시 기초
엔티티를 실제 사용하고 싶은 지점까지 미루고 싶을 때, EntityManager.getReference(Class<T> entityClass,
Object primaryKey)를 사용하면 된다.
- 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회한다.
- 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청하는데 이것을 초기화라 한다.
- 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다.
- 프록시 객체는 생성된 엔티티 객체의 참조를 Member target 멤버변수에 보관한다.
- 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.
특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
- 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. 즉, 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있다.
- 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시에 주의해서 사용해야 한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 entityManager.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.
- 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다. -> org.hibernate.LazyInitializationException 예외를 발생
- 엔티티를 프록시로 조회할 때 식별자 값을 파라미터로 전달하므로 식별자 값을 조회하는 ( ex - getId() )를 호출해도 프록시를 초기화 하지 않는다. (단, 엔티티 접근 방식으로 @Access(AccessType.PROPERTY)로 설정해야 한다.)
- EntityManager.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(Entity entity) -> boolean 반환형으로 프록시 인스턴스 초기화 여부를 알 수 있다.
- EntityManagerFactory.getPersistenceUnitUtil().isLoaded(Entity entity) -> boolean 반환형으로 프록시 인스턴스 초기화 여부를 알 수 있다.
즉시 로딩 - EAGER LOADING
- 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
- ex) @ManyToOne(fetch = FetchType.EAGER)
- 엔티티를 조회할 때 연관된 엔티티도 같이 조회하므로 쿼리를 2번 이상 실행할 것 같지만, 대부분 JPA는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다.
- 조인 쿼리를 사용하여 즉시 로딩할 때 null을 허용하면 외부 조인, null을 허용하지 않으면 내부 조인으로 된다.
지연로딩 - LAZY LOADING
- 연관된 엔티티를 실제 사용할 때 조회한다.
- ex) @ManyToOne(fetch = FetchType.LAZY)
- 엔티티를 조회할 때 연관된 엔티티가 영속성 컨텍스트에 없다면 프록시 객체로 조회하고 이 프록시 객체를 실제 사용할 때까지 데이터 로딩을 미룬다.
JPA 기본 페치 전략
- @ManyToOne, @OneToOne : 즉시 로딩(FetchType.EAGER)
- optional = false : 내부 조인
- optional = true : 외부 조인
- @OneToMany, @ManyToMany : 지연 로딩(FetchType.LAZY)
- optional = false : 외부 조인
- optional = true : 내부 조인
기본적으로 JPA 기본 페치 전략은 연관된 엔티티가 하나면 즉시 로딩을, 컬렉션이면 지연 로딩을 사용한다.
컬렉션을 로딩하는 것은 비용이 많이 들고 잘못하면 너무 많은 데이터를 로딩할 수 있기 때문이다.
즉, 컬렉션을 하나 이상 즉시 로딩하는 것은 권장하지 않는다.
영속성 전이 : CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 된다.
@Entity
public class Member {
@Id
@Column(name = "member_id")
private String id;
private String userName;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team {
@Id
@Column(name = "team_id")
private String id;
private String name;
@OneToMany(mappedBy = "team", cascade = CascadeType.PERSIST)
private List<Member> memberList = new ArrayList<>();
}
JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.
EntityManager.persist(Member)를 하면 CascadeType.PERSIST 로 인해 자동으로 Team 엔티티도 영속 상태가 된다.
즉, 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공한다.
Cascade 종류
public enum CascadeType {
/** Cascade all operations */
ALL,
/** Cascade persist operation */
PERSIST,
/** Cascade merge operation */
MERGE,
/** Cascade remove operation */
REMOVE,
/** Cascade refresh operation */
REFRESH,
/**
* Cascade detach operation
*
* @since Java Persistence 2.0
*
*/
DETACH
}
고아 객체
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데 이것을 고아 객체(ORPHAN) 제거라 한다.
부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제된다.
@Entity
public class Parent {
@Id
@GenerationValue
private Long id;
@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<Child>();
}
참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다.
만약 삭제한 엔티티를 다른 곳에서도 참조한다면 문제가 발생할 수 있으므로 @OneToOne, @OneToMany에만 사용할 수 있다.
'Spring JPA' 카테고리의 다른 글
객체지향 쿼리 언어 - 13 (0) | 2021.07.17 |
---|---|
값 타입 - 12 (0) | 2021.07.16 |
고급 매핑 - 10 (0) | 2021.07.12 |
다양한 연관관계 매핑 - 9 (0) | 2021.07.09 |
연관관계 매핑 기초 - 8 (0) | 2021.07.06 |
댓글