[JPA] 프록시 Proxy & LAZY,EAGER 로딩, 영속성 전이 CASCADE

손효재·2022년 7월 3일
0

JPA

목록 보기
4/11

김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의를 듣고 정리한 내용입니다.
https://www.inflearn.com/course/ORM-JPA-Basic

프록시

실제 클래스를 상속받아 만들어져서, 실제 클래스와 겉모양이 같다.
사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지않고 사용하면 된다.

프록시 객체는 실제 객체의 참조(target)을 보관하고있다.
프록시 객체를 호출하면 프록시 객체는 실제 객체의 메서드를 호출한다.

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

프록시 객체의 초기화

Untitled

초기에는 target에 null이고, 영속성 컨텍스트에 초기화를 요청한다.
이후 영속성 컨텍스트는 DB를 조회해서 실제 엔티티 객체를 생성하여 target에 연결시켜준다.
target으로 실제 객체와 연결되면, 실제 객체의 메서드를 호출한다.

프록시의 특징

  • 프록시 객체는 처음 사용할때 한번만 초기화한다.
  • 프록시 객체를 초기화할때, 프록시 객체가 실제 엔티티로 바뀌는것이 아니다!!
    초기화되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능한 것이다.
  • 프록시 객체는 원본 엔티티를 상속받기 때문에, 타입체크시 주의해야한다!! ( == 대신 instance of 사용)
    프록시 객체와 원본 객체는 == 비교시에 타입이 각각 프록시타입과 실제 타입으로 다르다!!

JPA는 하나의 트랜잭션 내에서 ==이 알맞게 동작하도록 상황에 따라 다르게 작동한다.

  1. 실제 Entity 조회 후 프록시 객체를 조회하는 경우
    영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티가 반환된다.
    이미 영속성 컨텍스트에 올라가있기때문에 굳이 프록시를 가져올 이유가없다.
    성능최적화 입장에서도 영속성 컨텍스트에 올라간 실제 엔티티를 가져오면 된다.
  2. 프록시 객체를 조회 후 실제 Entity를 조회하는 경우
    반대의 상황도 같다 (프록시가 먼저 초기화됐으면, em.find()를 호출해도 프록시 객체가 반환된다)

ex) 아래와 같은 코드에서 2번째 find한 findMember는 proxy객체가 반환된다.

Untitled (1)

→ 이처럼 JPA는 한 트랜잭션 내에서 실제 엔티티 객체와 프록시 객체의 비교연산 동작의 완전성을 보장하기 위해 두 객체의 클래스 타입이 동일하다. refMember 와 findMember의 == 은 true 이다.

but, 동일한 트랜잭션이 아닌경우, ==을 사용한다면 상황에 따라 결과가 달라질 수 있기 때문에 instance of 를 사용하는것이 좋다.

  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생

아래 코드와 같이 em.detach()나 em.close(), em.clear()로 영속성 컨텍스트에서 detach시키거나 닫아서
준영속 상태에서 getUsername을 호출하면 LazyInitializationException 예외가 발생한다.

Untitled (2)

프록시 확인 메서드

  • EntityManagerFactory.getPersistenceUnitUtil.isLoaded(Object entity) - 프록시 인스턴스의 초기화 여부 확인
  • entity.getClass().getName() 출력 - 프록시 클래스 확인 방법
  • Hibernate.initialize(entity) - 프록시 강제 초기화

참고로 JPA 표준은 강제 초기화가 없어서 강제로 호출해야한다.
강제 호출: member.getName()


즉시 로딩과 지연로딩

비즈니스로직상에서 단순히 회원 정보만 필요하고 팀 정보는 필요없을때, 회원을 조회할때 팀을 함께 조회하는것은 성능상 손해이다. 따라서 이를 지연로딩으로 설정하면, 연관관계에 관한 값을 요청할 때 DB에 query를 보내는 방식으로 동작한다.

지연로딩

연관관계 매핑에서 fetch = FetchType.LAZY 으로 설정된 엔티티는 프록시로 가져온다.

실제로 그 엔티티를 사용하는 시점에 초기화가 된다.(DB조회)

즉시 로딩

fetch = FetchType.EAGER 로 즉시 로딩하여 가져오기때문에 프록시를 쓸 일이 없다. 이미 초기화가 다 끝나있는 상태이다.

❗️ 주의

  • 가급적 지연 로딩만 사용해야 한다. (실무에서 즉시로딩은 사용하지 말자!!)
  • 즉시로딩을 적용하면 예상치못한 SQL이 발생하고, JPQL에서 N+1 문제를 일으킨다.
  • @ManyToOne, @OneToOne은 기본이 즉시로딩이기 때문에 LAZY로 설정해야한다.
  • @OneToMany, @ManyToMany는 기본이 지연 로딩이다.

영속성 전이 - CASCADE

특정 엔티티를 영속 상태로 만들때 연관된 엔티티도 함께 영속상태로 만들고 싶을때 사용한다.
ex) 부모 엔티티를 저장할때 자식 엔티티도 함께 저장

연관관계 매핑의 cascade 속성 - cascade=CascadeType.Cascade타입

CASCADE 종류

  • ALL : 모두 적용
  • PERSIST : 영속
  • REMOVE : 삭제

부모를 persist 할때, cascade 지정된 매핑된 엔티티도 함께 persist 해준다

엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음

하나의 엔티티가 해당 엔티티를 관리할때(종속적일때) 의미가 있다. 라이프 사이클이 동일하고, 단일 소유자일때 사용하면 된다. 다른 엔티티에서도 관리하고 있을때는 사용하면 안된다!!

고아객체

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제한다.
참조가 제거된 엔티티는 다른곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다.

연관관계 매핑의 고아객체 속성 - orphanRemoval = true

❗️ 주의

  • 참조하는 곳이 하나일때 사용해야한다.
  • 특정 엔티티가 개인 소유할 때 사용해야한다.
  • @OneToOne, @OneToMany만 가능

참고 : 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.

스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 생명주기를 제거한다.
두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.
CascadeType.ALL + orphanRemovel=true (영속성 전이 + 고아객체)

0개의 댓글