[자바 ORM 표준 JPA 프로그래밍] 8장_프록시와 연관관계 정리
다음 내용들을 정리한다.
- 프록시
- 즉시로딩, 지연 로딩
- 영속성 전이
- 고아 객체
프록시
아래 내용을 보기 전에, 우선 프록시에 대한 개념을 파악해야한다.
여기를 참고하자 : https://junhee-ko.github.io/2021/04/17/proxy-pattern/
이제, 예제를 보자.
1 |
|
printUserBy 메서드에서, 멤버를 조회할 때 연관된 팀 엔티티까지 DB 에서 같 조회하는게 효율적일까 ?
팀 엔티티를 실제 사용하는 시점에 조회하는게 효율적이다. 이것을 지연로딩이라고 한다.
엔티티를 실제 사용하는 시점까지 데이터베이스 조회를 미루고 싶으면 EntityManager.getReference() 를 사용하면 된다. 이 메서드는, 실제 엔티티 객체를 생성하지 않고 프록시 객체를 반환한다.
프록시 특징
프록시 객체는 실제 클래스를 상속 받아 만들어지며, 실제 객체에 대한 참조(target) 을 보관한다. 프록시 객체의 메소드를 호출하면, 프록시 객체는 실제 객체의 메소드를 호출한다.
프록시 객체의 초기화
1 | Member member = em.getReference(Member.class, "id1"); |
프록시 객체의 초기화란, member.getUsername() 처럼 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티를 생성하는 것이다. 다음 과정과 같다.
- 프록시 객체에 member.getUsername() 호출해서 실제 데이터 조회
- 프로시 객체는 실제 엔티티가 생성되어 있지 않으면, 영속성 컨텍스트에 실제 엔티티 생성 요청 (== 초기화 요청)
- 영속성 컨텍스트는 DB 조회해서 실제 엔티티 객체 생성
- 프록시 객체는 실제 엔티티 객체의 참조를 Member target 참조 변수에 보관
- 프록시 객체는 실제 엔티티 객체의 getUsername() 을 호출해서 결과 반환
주의할 점은,
- 영속성 컨텍스트에 이미 찾는 엔티티가 있으면, DB 를 조회할 필요가 없으므로 em.getReference() 를 호출해도 프록시가 아니라 실제 엔티티를 반환
- 초기화는 영속성 컨텍스트의 도움을 받아야 가능. 그래서, 준영속 상태의 프록시를 초기화하면 문제가 발생 (Hibernate : LazyInitializationException 발생)
즉시 로딩
1 |
|
멤버를 조회하는 순간 팀도 함께 조회한다. 여기서 주의할 점은 회원과 팀 두 테이블을 조회해야하므로 쿼리를 두 번 실행하는 것이 아니라, join query 를 사용한다는 것이다.
지연 로딩
1 |
|
만약, team 엔티티가 영속성 컨텍스트에 이미 로딩되어 있으면, 프록시가 아닌 실제 엔티티를 사용한다.
JPA 기본 Fetch 전략
- @ManyToOne, @OneToOne : FetchType.EAGER
- @OneToMany, @ManyToMany : FetchType.LAZY
연관된 엔티티가 하나면 즉시 로딩을, 컬렉션이면 지연로딩을 사용한다. 컬렉션을 로딩하는 것은 비용이 많이 들고 자칫하면 너무 많은 데이터를 로드할 수 있기 때문이다.
추천하는 방법은 모든 연관관계에 지연 로딩을 사용하는 것이다. 그리고, 실제 사용하는 상황을 보고 꼭 필요한 곳에 즉시 로딩을 사용하도록 최적화 하면 된다.
영속성 전이
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 된다. JPA 는 CASCADE 옵션으로 영속성 전이 기능을 제공힌디.
예를 들어, 부모 엔티티가 여러 자식 엔티티를 가지고 있다.
1 |
|
영속성 전이 : 저장
부모만 영속화하면 CascadeType.PERSIST 로 설정한 자식 엔티티까지 함께 영속화해서 저장한다.
1 |
|
영속성 전이 : 삭제
CascadeType.REMOVE 설정하고 부모 엔티티만 삭제하면 연관된 자식 엔티티까지 함께 삭제된다.
현재 예시로는, DELETE SQL 이 세 번 실행된다. (부모 + 자식01 + 자식02)
1 | Parent parent = em.find(Parent.class, 1L); |
영속성 전이 발생 시점
CascadeType.PERSIST 와 CascadeType.REMOVE 는 em.persist() 와 em.remove() 를 실행할 때 바로 전이가 발생하는 것이 아니라, 플러쉬를 호출할 때 발생한다.
고아 객체
JPA는 부모 엔티티와 연관 관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데, 이를 고아 객체 제거라고 한다. 아래 코드를 보자.
1 |
|
컬렉션에서 첫 번째 자식을 제거하면, orphanRemoval = true 옵션 설정으로 인해서 데이터베이스의 데이터도 삭제된다.
주의할 점은, 고아 객체 제거 기능은 영속성 컨텍스트를 flush 할 때 적용되므로, flush 시점에 DELETE SQL 이 실행된다.
참고로, 모든 자식 엔티티를 제거하려면 다음과 같이 하면된다.
1 | parent.getChildren.clear(); |
영속성 전이 + 고아객체
일반적으로 엔티티는 em.persist()로 영속화되고, em.remove()로 제거된다. 이것은 엔티티 스스로 생명주기를 관리한다는 뜻이다.
그런데, CascadeType.All + orphanRemove = true 를 동시에 사용하면? 부모 엔티티를 통해서 자식 엔티티의 생명주기를 관리할 수 있다.
자식을 저장하려면, 부모만 등록.
1 | Parent parent = em.find(Parent.class, id); |
자식을 삭제하려면, 부모만 삭제
1 | Parent parent = em.find(Parent.class, id); |
자바 ORM 표준 프로그래밍 <김영한>