Web/JPA

EP9. 프록시와 연관관계 관리

프록시

em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

 

실제 클래스를 상속받아 만들어져서 모양이 같다. 그래서 사용자입장에선 그냥 진짜인지 프록시인지 구분하지 않고 사용할 수 있다.

 

 

프록시 초기화

프록시 객체에서 메소드 하나를 호출하면 영속성 컨텍스트가 DB를 조회해 실제 엔티티를 생성한다.

그리고 프록시 객체는 실제 엔티티를 가리킨다.

 

프록시 특징

  • 프록시객체를 초기화할때 프록시객체가 실제 엔티티로 바뀌는게 아니다. 실제 엔티티에 접근이 가능한 것이다.
  • 실제 엔티티와 프록시끼리 타입비교시에 ==비교가 안되고 instance of 로 비교해야한다. (실제 엔티티가 아니라서 !)
  • 프록시가 언제들어올지 모르기때문에 엔티티끼리 비교할땐 instance of 비교로 통일한다.

 

실제 엔티티와 프록시의 타입 비교

코드

출력결과

 

실제 엔티티를 가져오고 그 엔티티를 참조(프록시)로 받아오려고 했을 때

코드

결과

프록시로 반환이 안되고 실제 엔티티가 반환된다.

 

그 이유는

1. 이미 영속성 컨텍스트에 들어갔던 것을 굳이 프록시로 반환할 필요가 없다.

2. JPA는 같은 트랜잭션 안에서 동일성을 보장한다.

 

 

프록시로 받아오고 동일한 PK로 실제 엔티티를 받아오려고 시도

코드

결과

프록시 엔티티가 반환된다.

그 이유

1. em.find() 지만 JPA는 동일성 보장을 위해 같은 트랜잭션 안에서 같은 PK면 같은 엔티티를 반환한다.

 

 

중간에 영속성 컨텍스트를 비우거나 꺼버린다면?

코드

프록시는 영속성 컨텍스트를 통해 초기화가 되기 때문에 그 전에

  • em.detach();
  • em.clear();
  • em.close(); -> 하이버네이트 버전 업데이트 후 읽기가능상태라서 refMember.getUsername() 이 가능하다.

엔티티매니져를 비우거나 꺼버리면 프록시 초기화가 일어나지 않아서 

LazyInitializationException 오류가 나오게 된다.

* 현재 하이버네이트 버전 업데이트가 되어서 트랜잭션이 살아있으면 em.close()를 호출해도 완전히 닫히지 않은 읽기 가능 상태가 된다.

 

 

프록시 확인 메소드 정리

프록시 인스턴스 초기화 여부 emf.getPersistenceUnitUtil().isLoaded(entity)
프록시 클래스 확인 entity.getClass()
프록시 강제 초기화 Hibernate.initialize(entity);

 

 

 

 

 

 

지연로딩

 

 

Member

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn
private Team team;

member 객체에서 team을 가져올때 Fetch Lazy를 하면 team을 프록시 객체로 가져온다.

 

 

실무에서는 모든 연관관계에 지연 로딩만 사용한다.

이유

  1. 즉시로딩은 예상치못한 SQL이 발생한다. 연관된 다른 테이블이 많다면 다 조인을 하게된다.
  2. JPQL에서 N+1 문제를 일으킨다.
    (N+1문제 : 1개의 쿼리로 인해 N 개의 추가쿼리가 발생)

@ManyToOne, @OneToOne -> default가 즉시로딩이므로 직접 지연로딩으로 설정해줘야한다.

 

 

 

 

 

CASCADE

특정 엔티티를 영속 상태로 만들 때 연관된 다른 엔티티도 영속상태로 만들고 싶을때 사용

 

주의

  • 연관관계를 매핑하는 것과 아무 관련이 없다.
  • Child의 소유자가 하나일때 (Parent 일 때)만 사용 가능하고 다른 곳에서도 Child를 알게되면 사용하면 안된다.

 

옵션 : cascade = CascadeType.XXX

  • ALL : 라이프 사이클을 동등하게 (저장, 삭제 까지)
  • PERSIST : persist 까지만

 

 

 

고아 객체

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제

 

orphanRemoval = XXXX

  • true
  • false

주의

Child의 소유자가 하나일때 (Parent 일 때)만 사용 가능하고 다른 곳에서도 Child를 알게되면 사용하면 안된다.

ex) 게시판의 첨부파일 등

 

 

 

CascadeType.ALL + orphanRemoval = true : 부모엔티티가 자식의 생명주기를 모두 관리한다

 

 

 

 

 

 

 

CascadeType.REMOVE     vs      orphanRemoval = true


얼핏 보면 위 두 개의 옵션은 같아보인다.

사실상 같다고 생각해도 괜찮지만 자세하게 들어가면 차이점이 있다.

 

CascadeType.REMOVE는 말그대로 부모객체의 "삭제"라는 동작이 자식객체에게 영향을 미치는 것이다.

CascadeType.REMOVE는 그래서 a.setB(null) 을 했을때 기존의 자식객체 B가 삭제되지 않는다.

 

하지만 orphanRemoval=true 일때는 자식객체에서 부모객체가 존재하는지를 참조로 확인하기때문에

a.setB(null) 을 했을때 기존의 자식객체 B가 삭제된다.

 

 

a.setB(null)

  • CascadeType.REMOVE : 자식객체 삭제 X
    (삭제 동작을 할때 작동하기 때문)
  • orphanRemoval=true : 자식객체 삭제 O
    (자식객체에서 부모객체를 참조로 확인하기 때문)

 

 

출처 : korean eagle님 블로그

'Web > JPA' 카테고리의 다른 글

CascadeType.REMOVE vs orphanRemoval = true  (0) 2021.02.13
EP10. 값 타입  (0) 2021.02.12
EP8. 고급 매핑  (0) 2021.02.09
EP7. 다양한 연관관계 매핑  (0) 2021.02.08
EP6. 연관관계 매핑 (실전예제)  (0) 2021.02.06