em.getReference()
em.find() 는 실행 시 영속성 컨텍스트에 없다면 db에 쿼리를 날려 데이터를 가져오지만 em.getReference() 는 쿼리가 나가지 않는다.
실제로 db에 저장되어 있는 데이터 필드에 객체가 접근하려할 때, 쿼리를 날려 가져온다.
데이터베이스 조회를 미루는 가짜 엔티티 객체를 반환한다.
프록시 특징
프록시 객체는 처음 사용할 때 한 번만 초기화
초기화 시, 실제 엔티티에 접근이 가능한 것이지, 프록시가 실제 엔티티로 바뀌는 것이 아님
따라서 타입 체크시 주의해야함 (== 비교 대신 instanceof 를 사용하자)
em.getReference() 시, 영속성 컨텍스트에 이미 찾으려는 엔티티가 존재하면 프록시가 아닌 실제 엔티티를 반환 (프록시의 성능 문제, 비교 문제 해결)
프록시를 갖고 있는 상황에서 갖는 데이터에 대해 em.find() 호출 시, find 도 프록시를 반환함 (비교 문제를 해결하기 위해서)
프록시 객체가 준영속 상태인 상황에서 프록시를 초기화하려할 때, 영속성 컨텍스트가 관리하지 않으므로 예외(LazyInitializationException)가 발생함
프록시 확인
프록시 인스턴스의 초기화 여부 확인
PersistenceUnitUtil.isLoaded(Object entity)
프록시 클래스 확인 방법
entity.getClass() 출력
프록시 강제 초기화
Hibernate.initialize(entity)
cf) JPA 표준에는 강제 초기화가 없음
지연 로딩
JPA의 프록시 객체를 활용하여 조인을 통해 연관된 데이터를 바로 불러오지 않고 필요 시 가져오는 방법
@ManyToOne 과 같은 연관관계 지정 어노테이션에 fetch 속성 값으로 FetchType.LAZY 를 주어 설정할 수 있음
연관된 객체를 같이 사용할 일이 많다면 지연 로딩의 방법은 쿼리가 두번 나누어 나가게 되므로 성능상 좋지 않음
즉시 로딩
프록시 객체를 활용하지 않고 연관된 데이터를 바로 가져와 사용하는 방법
fetch 속성에 FetchType.EAGER 를 주어 설정할 수 있음
연관된 객체를 자주 사용하지 않는다면 필요 없는 조인을 매번 발생시키므로 성능상 좋지 않음
프록시와 즉시로딩
실무에서는 가급적 지연 로딩만 사용하자
즉시 로딩을 적용하면 예상치 못한 SQL (ex. 하나의 테이블 조회했는데 전부 조인) 이 발생
즉시 로딩은 JPQL 에서 N + 1 문제를 일으킨다
@ManyToOne, @OneToOne 은 기본이 즉시 로딩
N+1 문제?
JPA에서 JPQL을 사용할 때,
JPQL 로 가져온 객체와 연관관계가 즉시 로딩으로 가져오도록 설정된 객체가 있을 경우, 한 테이블에서 N개의 행을 찾는 JPQL 1 개를 실행할 경우 N 번의 쿼리가 추가 발생
지연 로딩으로 N개의 데이터를 가져오더라도 N개의 데이터를 순회하며 연관된 객체에 접근하려 할 시 매 호출마다 N번의 쿼리가 실행됨
N+1 문제를 방지하기 위한 방법이 세 가지 존재함 (fetch join, EntityGraph, Batch size)
영속성 전이: CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶은 경우 사용
ex. 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
영속성 전이는 연관관계 매핑과는 아무 연관이 없음
여러번 영속하는 것을 한 번에 해주는 편리함만 제공
자식 엔티티를 다른 엔티티에서도 접근할 수 있거나, 부모 엔티티와 같이 저장, 삭제가 이루어지지 않는 경우 사용 시 운영에 어려움
CASCADE의 종류
ALL: 모두 적용
PERSIST: 영속만 전이
REMOVE: 삭제만 전이
MERGE, REFRESH, DETACH 등
고아 객체
부모 엔티티와 연관관계가 끊어진 자식 엔티티
연관관계 어노테이션에서 orphanRemoval 속성 값을 true로 사용 시 연관관계가 변경되었을 때(부모 엔티티가 삭제되거나, 부모 엔티티가 바뀌는 경우) 고아 객체가 남아있지 않도록 자동으로 제거해줌
영속성 전이와 마찬가지로 참조하는 곳이 하나일 때 사용해야함
특정 엔티티가 개인 소유할 때 사용해야함
cf. 부모 엔티티가 삭제될 때 자식 엔티티도 삭제되는 것은 CascadeType.REMOVE와 같다
영속성 전이 + 고아 객체
CascadeType.ALL + orphanRemoval
영속성 전이만 사용할 경우 부모 엔티티에서 참조하는 자식 엔티티를 삭제해도 삭제가 되지 않음
고아 객체 제거 방법만 사용할 경우 부모 엔티티 저장 시 자식 엔티티도 각각 저장해야함
따라서 두 가지를 조합하여 부모 객체에서 완전히 자식 객체의 생명주기를 관리할 수 있음
도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용
참고.
Aggregate
시스템이 기대하는 책임을 수행하면서 일관성을 유지하는 단위
명령을 수행하기 위해 함께 조회하고 업데이트해야 하는 최소 단위
Aggregate Root
도메인 규칙을 지키려면 애그리거트에 속한 모든 객체가 정상적인 상태를 가져야 하는데, 이 전체를 일관적인 상태로 관리하는 책임을 지는 엔티티
가장 중요한 역할은 애그리거트의 일관성이 깨지지 않게 하는 것
애그리거트 루트가 강제하는 도메인 규칙을 적용하려면, 애그리거트 외부에서 애그리거트에 속한 내부 객체를 직접 변경하면 안된다. 즉, 애그리거트 루트 엔티티의 식별자를 통해서만 접근하게 해야 함