프록시와 연관관계 관리

inho ha·2022년 5월 19일
0

자바 ORM 표준 JPA 프로그래밍

http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788960777330

프록시

객체는 데이터베이스에 저장되어 있기 때문에 객체 그래프로 연관된 객체들을 탐색할 때 마음껏 탐색하기 어렵다.
프록시를 사용하여 지연로딩을 이용하면 이 문제를 해결 할 수 있다.

엔티티를 조회할 때 연관된 엔티티들을 사용하지 않는 경우도 있기 때문에 엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 지연로딩을 사용한다.

실제 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체를 프록시 객체라고한다.

프록시 클래스는 실제 클래스를 상속 받아서 만들어지므로 실제 클래스와 겉 모양이 같다.

프록시 기초

em.find로 엔티티를 직접 조회하면 데이터베이스를 조회하게 된다.

이를 실제 사용하는 시점까지 조회를 미루고 싶으면 em.getReference 메소드를 사용하면 된다.
이 메소드는 데이터베이스 접근은 위임한 프록시 객체를 반환한다.

프록시 객체의 초기화

프록시 객체는 member.getName() 처럼 실제 사용될 때 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다.
이것이 프록시 객체의 초기화이다.

member.getNmae()을 호출하면
1. 영속성 컨텍스트에서 실제 데이터를 조회한다.
2. 없으면 영속성 컨텍스트에 실제 엔티티 생성을 요청한다.
3. 그럼 영속성 컨텍스트가 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다.
4. 프록시 객체는 생성된 실제 엔티티 객체의 참조를 멤버변수로 보관한다.
5. 프록시 객체가 실제 엔티티 객체의 getName를 호출해서 결과를 반환한다.

프록시의 특징

프록시 객체는 처음 사용할 때 한번만 초기화된다.

초기화 이후 프록시 객체가 실제 엔티티에 접근할 수 있다.

프록시 객체는 원본을 상속받은 객체라서 타입 체크할 때 주의해야한다.

영속성 컨텍스트에 찾으려는 엔티티가 이미 있으면 em.getReference를 호출해도 실제 엔티티를 반환한다.

초기화는 영속성 컨텍스트의 도움을 받아야 가능하다.
준영속 상태에서 초기화하면 예외가 발생한다.

프록시와 식별자

엔티티를 프록시로 조회할 때 식별자 값을 프록시 객체가 보관한다.
따라서 이 식별자 값을 조회하는 메소드를 호출해도 프록시를 초기화 하지 않는다. (엔티티 접근 방식을 프로퍼티로 설정한 경우에만)

프록시는 연관관계를 설정할 때 유용하게 사용할 수 있다.
연관관계를 설정할 때는 식별자 값만 사용하기 때문이다.

프록시 확인

PersistenceUnitUtil.isLoaded(Object entity) 메소드로 프록시 인스턴스의 초기화 여부를 확인할 수 있다.

즉시 로딩

엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
이때 연관된 테이블도 조회해야 하기 때문에 쿼리가 여러번 실행될 것 같지만 JPA 구현체는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다.

이때 조인은 left outer join으로 실행된다.
외래 키가 null을 허용하고 있는지 여부를 알수 없기 때문이다.

외래 키가 null허용하고 있지 않다면 @JoinColumn에 nullable = false 를 설정해서 JPA에게 알려주면 inner join으로 실행되어 성능과 최적화에서 더 유리하다.

즉시 로딩, 지연 로딩

필요할 때마다 SQL을 실행해서 연관된 엔티티를 지연 로딩하는 것도 최적화 관점에서 항상 좋은 것은 아니다.
연관된 엔티티를 같이 사용한다면 즉시 로딩으로 한번에 조회하는 것이 효율적이다.

프록시와 컬렉션 래퍼

하이버네이트는 엔티티를 영속 상태로 만들 때 엔티티에 컬렉션이 있으면 컬렉션을 추적하고 관리할 목적으로 원본 컬렉션을 하이버네이트가 제공하는 내장 컬렉션으로 변경한다.
이것을 컬렉션 래퍼라 한다.

엔티티를 지연 로딩하면 엔티티의 컬렉션은 컬렉션 래퍼가 지연 로딩 처리해준다.
컬렉션 래퍼가 컬렉션에 대한 프록시 역할을 해준다.

member.getOrders()를 호출해도 컬렉션은 초기화 되지 않고
member.getOrders().get(0) 처럼 컬렉션에서 실제 데이터를 조회할 때 데이터 베이스를 조회해서 초기화한다.

JPA 기본 페치 전략

@ManyToOne, @OneToOne 은 즉시 로딩
@OneToMany, @ManyToMany 는 지연 로딩이 기본 값이다.

즉 연관된 엔티티가 하나면 즉시 로딩이, 컬렉션이면 지연 로딩이 기본 값이다.

컬렉션을 로딩하는 것은 비용이 많이 들고 너무 많은 데이터를 로딩할 수 있기 때문이다.

그러나 추천하는 방법은 모두 지연 로딩을 사용하는 것이다.
이후 꼭 필요한 곳에만 즉시 로딩을 사용하도록 최적화 하면 된다.
이때 SQL을 직접 사용하면 유연한 최적화가 어렵다.

컬렉션에 즉시 로딩 사용시 주의점

컬렉션을 하나 이상 즉시 로딩하는 것은 권하지 않는다.

컬렉션과 조인한다는 것은 일대다 조인이다.
이때 결과 데이터는 다의 수만큼 증가하게 된다.
서로 다른 컬렉션을 2개 이상 조인하게 되면 다*다 로 엄청 많은 데이터가 반환될 수 있다.
JPA는 이렇게 조회된 결과를 메모리에서 필터링해서 반환한다.

컬렉션 즉시 로딩은 항상 outer join을 사용한다.

팀과 회원의 경우에 회원 테이블의 외래 키에 not null 제약조건을 걸어 내부 조인을 사용해도 된다.
그러나 팀에서 회원으로 조회할 때는 회원이 없는 팀의 경우 inner join을 하게되면 조회되지 않는다.
따라서 일대다를 즉시 로딩할 때는 outer join을 사용해야한다.

영속성 전이 (CASCADE)

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만드는 기능이다.

영속성 전이로 저장

@OneToMany(cascade = CascadeType.PERSIST)
로 설정하면 해당 엔티티를 저장할 때 이렇게 설정한 연관된 엔티티도 영속화해서 저장한다.

영속성 전이로 삭제

@OneToMany(cascade = CascadeType.REMOVE)
로 설정하면 해당 엔티티를 삭제할 때 이렇게 설정한 연관된 엔티티도 함께 삭제한다.

고아 객체

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 고아 객체라고 한다.
JPA는 고아 객체 제거 기능을 제공한다.
@OneToMany(orphanRemoval = true)
로 설정하면 고아 객체 제거 기능이 활성화 된다.

이 기능은 참조하는 곳이 하나일 때만 사용해야한다.

영속성 전이 + 고아 객체

CascadeType.All + orphanRemoval = true 를 동시에 사용하면 엔티티 스스로 생명주기를 관리한다는 뜻이다.

자식을 저장하려면 부모에 등록하면 되고
자식을 삭제하려면 부모에서 제거하면 된다.

profile
inho ha / ian(swatchon) / iha(42seoul)

0개의 댓글