프록시#
- 지연 로딩: 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법
- 지연 로딩 기능을 사용하려면 실제 엔티티 객체 대신 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체라 한다.
- JPA 표준 명세는 지연로딩의 구현 방법을 JPA 구현체에 위임했다.
- 하이버네이트는 지연 로딩을 지원하기 위해 프록시를 사용하는 방법과 바이트코드를 수정하는 두 가지 방법을 제공한다.
프록시 기초#
- 엔티티를 직접 조회하면 조회한 엔티티를 실제 사용하든 사용하지 않든 데이터베이스를 조회하게 된다.
Member member = em.find(Member.class, "member1");
- 엔티티를 실제 사용하는 시점까지 데이터베이스 조회를 미루고 싶으면
EntityManager.getReference()
메소드를 사용하면 된다.
Member member = em.getReference(Member.class, "member1");
- 프록시 클래스는 실제 클래스를 상속 받아서 만들어지므로 실제 클래스와 겉 모양이 같다.
- 프록시 객체는 실제 객체애 대한 참조를 보관한다.
- 그리고 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.

member.getName()
처럼 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성하는데 이것을 프록시 객체의 초기화라 한다.- 프록시의 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
- 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다.
- 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시에 주의해서 사용해야 한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로
em.getReference()
를 호출해도 프록시가 아닌 실제 엔티티를 반환한다. - 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면
LazyIntializiationException
예외를 발생시킨다.
프록시와 식별자#
- 엔티티를 프록시로 조회할 때 식별자 값을 파라미터로 전달하는데 프록시 객체는 이 식별자 값을 보관한다.
- 따라서, 엔티티 접근 방식을 프로퍼티(
@Access(AccessType.PROPERTY)
)로 설정한 경우에는 team.getId()
를 호출해도 프록시를 초기화하지 않는다. - 엔티티 접근 방식을 필드(
@Access(AccessType.FIELD)
)로 설정하면 JPA는 getId()
메소드가 id만 조회하는 메소드인지 다른 필드까지 활용해서 어떤 일을 하는 메소드인지 알지 못하므로 프록시 객체를 초기화한다.
프록시 확인#
- JPA가 제공하는
PersistenceUnitUtil.isLoaded(Object entity)
메소드를 사용하면 프록시 인스턴스의 초기화 여부를 확인할 수 있다. - 조회한 엔티티가 진짜 엔티티인지 프록시로 조회한 것인지 확인하려면 클래스명을 직접 출력해보면 된다.
- 클래스 명 뒤에 ..javassist.. 라 되어 있으면 프록시인 것을 확인할 수 있다.

- 하이버네이트의 경우
initialize()
메소드를 사용하면 프록시를 강제로 초기화할 수 있다.
- JPA 표준에는 프록시 강제 초기화 메소드가 없다.
즉시 로딩과 지연 로딩#
- 즉시 로딩: 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
- 지연 로딩: 연관된 엔티티를 실제 사용할 때 조회한다.
즉시 로딩#
@ManyToOne
의 fetch 속성을 FetchType.EAGER
로 지정한다.- 대부분의 JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다.
@JoinColumn
에 nullable=false
을 설정해서 외래 키는 NULL을 허용하지 않는다고 알려주면 JPA는 외부 조인 대신 내부 조인을 사용한다.@ManyToOne.optional = false
로 설정해도 내부 조인을 사용한다.
지연 로딩#
@ManyToOne
의 fetch 속성을 FetchType.LAZY
로 지정한다.
즉시 로딩, 지연 로딩 정리#
- 처음부터 연관된 엔티티를 모두 영속성 컨텍스트에 올려두는 것은 현실적이지 않다.
- 필요할 때마다 SQL을 실행해서 연관된 엔티티를 지연 로딩하는 것도 최적화 관점에서 보면 꼭 좋은 것은 아니다.
- 대부분의 애플리케이션 로직에서 회원과 팀 엔티티를 같이 사용한다면 SQL 조인을 사용해서 회원과 팀 엔티티를 한 번에 조회하는 것이 더 효율적이다.
- 결국 상황에 따라 다르게 선택해서 적용해야 한다.
지연 로딩 활용#
프록시와 컬렉션 래퍼#
- 하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티에 컬렉션이 있으면 컬렉션을 추적하고 관리할 목적으로 원본 컬렉션을 하이버네이트가 제공하는 내장 컬렉션으로 변경하는데 이것을 컬렉션 래퍼라 한다.
- 컬렉션 래퍼도 컬렉션에 대한 프록시 역할을 하므로 컬렉션에 대한 지연 로딩을 처리해준다.
member.getOrders()
를 호출해도 컬렉션은 초기화되지 않는다. 컬렉션은 member.getOrders().get(0)
처럼 실제 데이터를 조회할 때 데이터베이스를 조회해서 초기화 한다.
JPA 기본 페치 전략#
- JPA의 기본 페치 전략
@ManyToOne
, @OneToOne
: 즉시 로딩(FetchType.EAGER
)@OneToMany
, @ManyToMany
: 지연 로딩(FetchType.LAZY
)
- 추천하는 방법은 모든 연관관계를 지연 로딩을 사용하는 것이다.
- 애플리케이션 개발이 어느 정도 완료단계에 왔을 때 실제 사용하는 상황을 보고 꼭 필요한 곳에만 지연로딩을 사용하도록 최적화하면 된다.
- SQL을 직접 사용하면 이런 유연한 최적화가 어렵다.
컬렉션에 FetchType.EAGER 사용 시 주의점#
- 컬렉션을 하나 이상 즉시 로딩하는 것을 권장하지 않는다.
- JPA는 조회된 결과를 메모리에서 필터링해서 반환한다.
- 애플리케이션 성능이 저하될 수 있다.
- 컬렉션 즉시 로딩은 항상 외부 조인을 사용한다.
- 회원과 팀 테이블이 있고, 회원 외래 키에
not null
제약조건을 걸어두면 - 모든 회원은 팀에 소속되므로 항상 내부 조인을 해도된다.
- 반대로 팀 테이블에서 회원 테이블로 일대다 관계를 조인할 때는 회원이 한 명도 없는 팀을 내부 조인하면 팀이 조회되지 않으므로 문제가 된다.
@ManyToOne
, @OneToOne
- optional = false: 내부 조인
- optional = true: 외부 조인
@OneToMany
, @ManyToMany
- optional = false: 외부 조인
- optional = true: 외부 조인
영속성 전이: CASCADE#
- 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 된다.
- 아래는 영속성 전이를 사용하지 않는 경우다.
- 연관된 모든 엔티티를 영속 상태로 만들어야 된다.

영속성 전이: 저장#
- 영속성 전이는 연관관계를 매핑하는 것과는 아무 관련이 없다.
- 단지 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공할 뿐이다.


영속성 전이: 삭제#
- 엔티티를 삭제할 때도
CascadeType.REMOVE
로 영속성 전이가 가능하다.
CASCADE 종류#

CascadeType.PERSIST
와 CascadeType.REMOVE
는 em.persist()
, em.remove()
를 실행할 때 바로 전이가 발생하지 않고 플러시를 호출할 때 전이가 발생한다.
고아 객체#
- 고아 객체 제거 기능: 참조가 제거된 엔티티가 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제한다.
orphanRemoval = true
옵션으로 켤 수 있다.- 영속성 컨텍스트가 플러시할 때 적용된다.
- 이 기능은 참조하는 곳이 하나뿐인 엔티티에만 사용해야 된다.
- 이러한 이유로
@OneToOne
, @OneToMany
에만 사용할 수 있다.
- 부모 엔티티가 제거되어도 자식이 고아가 되어서 같이 제거된다.
영속성 전이 + 고아 객체, 생명주기#
CascadeType.ALL
+ orphanRemoval = true
를 동시에 사용하면 어떻게 될까?- 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.
- 자식을 저장하면 부모에 등록만 하면 된다.
- 자식을 삭제하려면 부모에서 제거하면 된다.
- 영속성 전이는 DDD의 Aggregate Root 개념을 구현할 때 사용하면 편리하다.