#08 프록시와 연관관계 관리
김영한 저 "자바 ORM 표준 JPA 프로그래밍, 2015"을 읽고 정리한 내용입니다.
프록시와 즉시로딩,지연로딩
객체가 데이터베이스에 저장되어 있으므로 연관된 객체를 마음껏 탐색하기 어렵다
프록시라는 기술을 사용
실제 사용하는 시점에 데이터베이스에서 조회
영속성 전이와 고아 객체
연관된 객체를 함께 저장, 삭제할 수 있는 영속성 전이(transitive persstence)와 고아 객체 제거
8.1 프록시
엔티티를 조회할 때 연관된 엔티티들이 항상 사용되는 것은 아니다. 연관관계의 엔티티는 비즈니스 로직에 따라 사용될 때도 있지만 그렇지 않을 때도 있다.
// CASE 1. Member, Team 객체 조회 필요
public void printUserAndTeam(String memberId) {
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름: " + member.getUsername());
System.out.println("소식팀: " + team.getName()); // team 객체 조회
}
// CASE 2. Member 객체 조회 필요
public void printUser(String memberId) {
Member member = em.find(Member.class, memberId);
Team team = member.getTeam();
System.out.println("회원 이름: " + member.getUsername());
}team.getName()실제 사용하는 시점에 데이터를 조회지연 로딩 기능을 사용하려면, 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체라 한다
지연로딩 방법으로 프록시 말고 바이트코드를 수정하는 방법이 있으나 복
8.1.1 프록시 기초
프록시의 특징
프록시 객체는 처음 사용할 때 한 번만 초기화
프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니
다. 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있다.
프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시 주의
영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로
em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다.
준영속상태 초기화
영속성 컨텍스트를 종료해서 member는 준영속 상태. 예외발생
8.1.2 프록시와 식별자 - 어렵다 나중에 다시보기 294p
프록시 객체는 식별자 값을 가지고 있으므로 식별자 값을 조회하는 team.getId()를 호출해도 프록시를 초기화하지 않는다. 단, @Access(AccessType.PROPERTY)로 설정 한 경우에 만 초기하지 않는다. @Access(AccessType.FIELD)는 다른 필드까지 활용해서 어떤 일을 하는 메소 드인지 알지 못하므로 프록시 객체를 초기화한다.
8.1.3 프록시 확인
PersistenceUnitUtil().isLoaded(entity)프록시 인스턴스의 초기화 여부를 확인
진짜 엔티티인지 프록시로 조회한 것인지 확인
_javassist_
JPA 표준에는 프록시 강제 초기화 메소드가 없다. 따라서 강제로 초기화하려면 member.getName()처럼 프록시의 메소드를 직접 호출하면 된다. JPA 표준은 단지 초기화 여부만 확인 할 수 있다.
8.2 즉시 로딩과 지연 로딩
즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
설정 방법 :
@ManyToOne(fetch = FetchType.EAGER)
지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.
설정 방법 :
@ManyToOne(getch = FetchType.LAZY)
8.2.1 즉시 로딩
fetch = FetchType.EAGER
회원과 팀 조회
외부 조인 (LEFT OUTER JOIN)을 사용한 것을 유심히 봐야 한다.
외부 조인보다 내부 조인이 성능과 최적화에서 더 유리하다.
내부조인 사용 - 외래 키에 NOT NULL 제약조건을 설정
8.2.2 지연 로딩
fetch = FetchType.LAZY
8.3 지연로딩 활용
8.3.1프로식와 컬렉션 매퍼
엔티티를 영속 상태로 만들 때 엔티티에 컬렉션이 있으면 컬렉션을 추적하고 관리할 목적으로 원본 컬렉션을 내장 컬렉션으로 변경하는데, 이것을 컬렉션 래퍼라 한다.
member.getOrders()컬렉션은 초기화 x초기화란 영속성컨택스트에 DB조회하라 요청하는것
member.getOrders().get(0)이때는 초기화
8.3.2 JPA 기본 페치 전략
@ManyToOne, @OneToOne: 즉시 로딩(FetchType.EAGER)@OneToMany, @ManyToMany: 지연 로딩(FetchType.LAZY)
추천하는 방법은 모든 연관관계에 지연 로딩을 사용하고 개발이 어느 정도 완료단계에 왔을 때 상황 보고 필요 한 곳에만 즉시 로딩을 사용하도록 최적화하면 된다.
8.3.3 컬렉션에 FetchType.EAGER 사용 시 주의점
컬렉션을 하나 이상 즉시 로딩하는 것은 권장하지 않는다.
예를 들어, A 테이블을 N, M 두 테이블과 일대다 조인하면 SQL 실행 결과가 N 곱하기 M이 되면서 너무 많은 데이터를 반환할 수 있고 결과적으로 애플리케이션 성능이 저하될 수 있다. 따라서 2개 이상의 컬렉션을 즉시 로딩으로 설정하는 것은 권장하지 않는다.
컬렉션 즉시 로딩은 항상 외부 조인을 사용한다.
데이터베이스 제약조건으로 내부 조인으로 인해 검색이 되지 않는 상황을 막을 수는 없다. 따라서 JPA는 일대다 관계를 즉시 로딩할 때 항상 외부 조인을 사용한다.
FetchType.EAGER 설정과 조인 전략
@ManyToOne, @OneToOne
(optional = false) : 내부 조인
(optional = true) : 외부 조인
@OneToMany, @ManyToMany
(optional = false) : 외부 조인
(optional = true) : 외부 조인
8.4 영속성 전이: CASCADE
특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이(transitive persistence) 기능을 사용하면 된다. JPA는 CASCADE 옵션으로 영속성 전이를 제공한다.
@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
8.4.1 저장,삭제
부모만 영속화하면
CascadeType.PERSIST로 설정한 자식 엔티티까지 함께 영속화해서 저장한다.영속성 전이는 연관관계를 매핑하는 것과는 아무 관련이 없다. 단지 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공할 뿐이다.
8.4.2 CASCADE의 종류
PERSIST, MREOMOVE는 em.persist(), em.remove()를 실행할 때 바로 전이가 발생하지 않고 플러시를 호출할 때 전이가 발생한다.
8.5 고아 객체
부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능-
orphanRemoval = true
고아 객체 제거 기능은 영속성 컨텍스트를 플러시할 때 적용된
참조하는 곳이 하나일 때만 사용한. 쉽게 이야기해서 특정 엔티티가 개인소유 하는 엔티티에만 이 기능을 적용해야 한다. 이런 이유로 orphanRemovel은 @OneToOne, @OneToMany에만 사용한다.
8.6 영속성 전이 + 고아 객체, 생명주기
CascadeType.ALL + orphanRemoval = true를 동시에 사용하면 어떻게 될까?
일반적으로 엔티티는 EntityManager.persist()를 통해 영속화되고 EntityManager.remove()를 통해 제거된다. 이것은 엔티티 스스로 생명주기를 관리한다는 뜻이다. 그런데 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.
영속성 전이는 DDD의 Aggregate Root개념을 구현할 때 사용하면 편리하다
정리
JPA구현체들은 객체 그래프를 마음껏 탐색할 수 있도록 지원하는데 이때 프록시 기술을 사용한다.객체를 조회할 때 연관된 객체를 즉시 로딩하는 방법을
즉시 로딩이라 하고, 연관된 객체를 지연해서 로딩하는 방법을지연 로딩이라 한다.객체를 저장하거나 삭제할 때 연관된 객체도 함께 저장하거나 삭제할 수 있는데 이것을
영속성 전이라 한다.부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하려면
고아 객체 제거 기능을 사용하면 된다.
[실전 예제]
모두 지연로딩으로 설정
@ManyToOne (fetch = FetchType.LAZY)
@OneToOne(fetch = FetchType.LAZY)
Transitive persistence 설정
@OneToOne( ... , cascade = CascadeType.ALL)
@OneToMany( ... , cascade = CascadeType.ALL)
Last updated