본문 바로가기
Spring

[JPA] 프록시

by runlearn 2023. 10. 9.

중요 포인트

  • Team과 Member(1:N) 연관관계를 가지는 객체가 존재할 때. 데이터를 조회시 둘다 가져와야할지 하나만 가져와야 할지 상황에따라 애매한 경우가 존재한다.
  • jpa에서 이런 상황은 즉시 로딩과 지연 로딩에 대해 고민해야 할 문제이다. 두 개념을 알기 전에 프록시 개념의 이해가 필요하다.

프록시

프록시의 초기화 요청은 영속성 컨텍스트를 통해 이루어진다.

em.find() vs em.getPersistence()

지연로딩, 즉시로딩 , 프록시
프록시 클래스 - 가짜 객체

주의1

프록시 객체는 처음 사용할 때 한 번만 초기화 된다. 프록시 객체가 초기화되는 것이 프록시가 진짜 객체로 바뀌는 것을 의미하지 않는다.
프록시 객체 내부의 target에 값이 초기화 되는 것.
프록시 객체의 타입 체크시 주의해야한다. == 비교대신 instanceof 사용해야함

주의2

JPA에서는 같은 트랜잭션안에서 동일한 식별자로 조회한 엔티티는 동일한 주소를 가진다. 실제든 프록시든 관계 없이(== 연산시 true)

주의3

영속성 컨텍스트에 실제 엔티티가 존재하는 경우 em.getPersistence() 로 조회시 실제 엔티티 반환
만약 em.getPersistence()먼저 호출 후 em.find()호출하는 경우 find()를 위한 sql이 실행되지만, 프록시 객체를 반환한다. jpa에서 같은 트랜잭션내에서 조회시 == 결과가 true를 보장해야 하기 때문이다. (실무에서 볼 일은 거의 없음)

주의4

영속성 컨텍스트의 관리를 받지않는 상태 (준영속..) 프록시 객체를 초기화하면 에러가 발생한다.
프록시 객체도 영속성 컨텍스트를 통해 초기화를 하는데, 해당 엔티티가 영속성 컨텍스트의 관리를 받고 있지 않는 경우 초기화를 하지 못해 에러가 발생한다.

보통 트랜잭션 끝나고 엔티티를 조회할 때 접할 수 있다.

즉시로딩과 지연로딩

단순히 member정보만 조회해야 하는데 team엔티티의 정보까지 같이 조회한다면 불필요한 쿼리가 발생하게 된다.

연관관계의 fetchType의 설정을 주어 전부 조회할지, 필요한 데이터만 조회할지를 즉시 로딩과 지연 로딩으로 설정할 수 있다.

지연 로딩

fetchType을 LAZY로 두면 프록시로 조회하여 해당 엔티티만 조회한다. 그리고 해당 프록시 객체를 실제 사용할 때 실제 쿼리가 발생한다.
한방 쿼리가 아니어서 즉시로딩 보다 네트워크를 더 타는 단점이 있다.

즉시 로딩

fetchType이 EARGER 프록시 객체가 아닌 실제 엔티티를 조회한다. 조인을 통해 한방 쿼리로 조회한다.
대부분의 로직이 같이 조회한다면 EARGER을 사용을 고려해도 괜찮다. ex) member조회시 team까지 항상 같이 조회하는 경우

⭐️주의
  1. 하지만 실무에서는 즉시 로딩은 사용하지 말자. 즉시로딩을 사용하면 내가 예상하지 못한 쿼리가 발생할 수 있다.
  2. JPQL을 사용하는 경우 N+1문제를 일으킨다.
    1. JPQL -> SQL 로 변경되서 실행된다. ‘select m from Member m’ 로 조회하는 경우 Member엔티티에 Team이 즉시 로딩으로 설정된 경우 다시 Team을 조회하는 문제가 발생한다. 최초 쿼리 1개 실행했는데 Team을 조회하는 N개의 쿼리가 발생하는 것이다.
    2. Lazy로 설정해도 team을 사용하게 되면 쿼리가 발생한다. 이 문제는 fetch join으로 해결 할 수 있다.
  3. @ManyToOne, @OneToOne (One으로 끝남) 은 기본이 즉시 로딩이다. -> LAZY로 변경하여 사용할 것 , @OneToMany, @ManyToMany 의 경우 기본이 지연 로딩

영속성 전이와 고아 객체

영속성 전이 : 특정 엔티티를 영속화 할 때, 연관된 엔티티도 영속 상태로 만드는 것 (즉시로딩, 지연로딩, 연관관계와 관계 없다.)
@Test
void test4() {
Child child1 = new Child();
Child child2 = new Child();

Parent parent = new Parent();
parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);
em.persist(child1);
em.persist(child2);
}
  • 위의 코드에서 child1,child2 영속화 하는 코드가 없다면 parent만 인서트된다. 만약 위의 하나의 코드를 영속화 할 때 관련 엔티티도 영속화 하려면 어떻게 해야할까?
    • Parent클래스에 cascade = CascadeType.ALL를 추가해 주면 된다.
      @OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", cascade = CascadeType.ALL)  
      private List childs = new ArrayList<>();
  • CASCADE 종류 / ALL, PERSIST 정도만 사용하자.
    • ALL : 모두 적용
    • PERSIST : 영속
    • REMOVE : 삭제
  • 하나의 부모가 자식들을 관리할 때 의미 있음. 게시판과 첨부 파일과 같은 경우 의미 있다. 하지만 파일을 여러 엔티티에서 관리하는 경우 사용하면 안된다. 즉, 자식 엔티티가 하나의 부모 엔티티와 관계가 있을 때 (라이프 사이클이 같을 때) 사용해야 한다.

고아 객체

고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제

orphanRemoval설정을 true로 주면 고아 객체를 제거한다.

@OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", 
cascade = CascadeType.ALL,orphanRemoval = true)  
private List childs = new ArrayList<>(); 

이 기능 역시 참조하는 곳이 하나일 때 사용해야한다. 특정 엔티티가 개인 소유할 때 사용
CascadeType.REMOVE 처럼 동작한다.

영속성 전이 + 고아 객체, 생명주기

  • CascadeType.ALL + orphanRemoval = true
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
  • 위의 옵션을 활성화하여 부모 엔티티를 통해 자식 엔티티릐 생명주기를 관리한다.
  • DDD의 애거리거트 루트 개념 구현시 유용

 

참고

  • 자바 ORM 표준 JPA 프로그래밍 - 기본편

'Spring' 카테고리의 다른 글

[JPA]영속성 컨텍스트  (0) 2023.09.24
[JPA] 더티체킹  (0) 2023.09.11
Spring boot에서 Redis 연동  (0) 2023.07.24
Spring profile  (0) 2023.07.09

댓글