6

yeoro·2021년 9월 28일
0

프록시와 연관관계 정리

프록시

프록시 기초

  • em.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
    -
  • em.getReference() : 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
    - 객체 조회시에는 sql 실행X, 값을 출력할 때 sql 실행
    • 실제로 클래스이름 출력하면 프록시 클래스가 출력됨

프록시 특징

  • 실제 엔티티를 상속받아서 만들어지기 때문에 실제 엔티티와 겉 모습이 같다.
  • 이론상, 사용자 입장에서는 진짜인지 프록시인지 구분하지 않고 사용
  • 프록시 객체는 실제 객체의 참조를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니다. (초기화 전과 후의 프록시 클래스명이 같음)
  • 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근하는 것이다.
  • 프록시 객체는 원본 엔티티를 상속 받고, 객체를 전달하는 과정에서 프록시인지 진짜 객체인지 모르기 때문에 타입 비교할 때는 instanceof를 사용한다.
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다. (영속성 컨텍스트 내에서는 동일성을 보장한다..?) 어떻게든 프록시와 엔티티 타입의 == 연산을 true로 보장한다?!
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태(em.detach, em.close)일 때, 프록시를 초기화하면 에러 발생

프록시 객체 초기화

  1. 사용자가 getName() 요청
  2. 프록시 객체가 영속성 컨텍스트에게 초기화 요청
  3. 영속성 컨텍스트가 DB 조회
  4. DB 조회 후 실제 Entity 객체 생성
  5. 프록시 객체는 생성된 Entity 객체를 참조하여 getName() 요청

프록시 확인

프록시 인스턴스의 초기화 여부 확인

EntityManagerFactory.getPersistenceUnitUtil().isLoaded(Object Entity);

프록시 클래스 확인

entity.getClass();

프록시 강제 초기화

Hibernate.initialize(entity);

즉시 로딩과 지연 로딩

Member를 조회할 때 Team도 함께 조회해야 할까?

단순히 Member 정보만 조작하는 로직이라면 필요 없을지도..?

지연 로딩 LAZY

과정

  1. Member 로딩
  2. Team 지연로딩 및 프록시 객체 생성
  3. Team 객체 조회시 SQL 실행 및 프록시 객체 초기화

사용

비즈니스 로직상 Member만 자주 사용하는 경우

즉시 로딩 EAGER

과정

  1. Member 로딩
  2. Team 즉시로딩 및 SQL 실행

사용

  • 모든 연관관계에 지연 로딩 사용할 것!

프록시와 즉시로딩 주의!

가급적 실무에서 지연 로딩만 사용

테이블이 수십 개 이상이면 많은 테이블에서 JOIN이 일어나서 성능 저하

즉시 로딩을 적용하면 예상치 못한 SQL 발생

JPQL에서 N+1 문제 (의도한 쿼리 1 + 의도치 않은 쿼리 N)

  • 일반적인 즉시 로딩을 통한 조회 (Member와 Team을 한 번에 조회)
Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_4_0_,
        member0_.INSERT_MEMBER as INSERT_M2_4_0_,
        member0_.createdDate as createdD3_4_0_,
        member0_.UPDATE_MEMBER as UPDATE_M4_4_0_,
        member0_.lastModifiedDate as lastModi5_4_0_,
        member0_.LOCKER_ID as LOCKER_I7_4_0_,
        member0_.name as name6_4_0_,
        member0_.TEAM_ID as TEAM_ID8_4_0_,
        locker1_.id as id1_3_1_,
        locker1_.MEMBER_ID as MEMBER_I3_3_1_,
        locker1_.name as name2_3_1_,
        member2_.MEMBER_ID as MEMBER_I1_4_2_,
        member2_.INSERT_MEMBER as INSERT_M2_4_2_,
        member2_.createdDate as createdD3_4_2_,
        member2_.UPDATE_MEMBER as UPDATE_M4_4_2_,
        member2_.lastModifiedDate as lastModi5_4_2_,
        member2_.LOCKER_ID as LOCKER_I7_4_2_,
        member2_.name as name6_4_2_,
        member2_.TEAM_ID as TEAM_ID8_4_2_,
        team3_.TEAM_ID as TEAM_ID1_8_3_,
        team3_.INSERT_MEMBER as INSERT_M2_8_3_,
        team3_.createdDate as createdD3_8_3_,
        team3_.UPDATE_MEMBER as UPDATE_M4_8_3_,
        team3_.lastModifiedDate as lastModi5_8_3_,
        team3_.name as name6_8_3_ 
    from
        Member member0_ 
    left outer join
        Locker locker1_ 
            on member0_.LOCKER_ID=locker1_.id 
    left outer join
        Member member2_ 
            on locker1_.MEMBER_ID=member2_.MEMBER_ID 
    left outer join
        Team team3_ 
            on member2_.TEAM_ID=team3_.TEAM_ID 
    where
        member0_.MEMBER_ID=?
  • JPQL 사용하여 조회 (Member 1번, Team 1번 총 2번 조회)
Hibernate: 
    /* select
        m 
    from
        Member m */ select
            member0_.MEMBER_ID as MEMBER_I1_4_,
            member0_.INSERT_MEMBER as INSERT_M2_4_,
            member0_.createdDate as createdD3_4_,
            member0_.UPDATE_MEMBER as UPDATE_M4_4_,
            member0_.lastModifiedDate as lastModi5_4_,
            member0_.LOCKER_ID as LOCKER_I7_4_,
            member0_.name as name6_4_,
            member0_.TEAM_ID as TEAM_ID8_4_ 
        from
            Member member0_
Hibernate: 
    select
        team0_.TEAM_ID as TEAM_ID1_8_0_,
        team0_.INSERT_MEMBER as INSERT_M2_8_0_,
        team0_.createdDate as createdD3_8_0_,
        team0_.UPDATE_MEMBER as UPDATE_M4_8_0_,
        team0_.lastModifiedDate as lastModi5_8_0_,
        team0_.name as name6_8_0_ 
    from
        Team team0_ 
    where
        team0_.TEAM_ID=?
  • 이유
    - JPQL은 그대로 SQL로 번역이 된다. 그러면 Member만 select
    • Member를 select했는데, Team이 즉시로딩이네? -> 가져올 때 값이 다 있어야하니까 Team을 가져오는 SQL 발생
    • 먼저 select * from Member
    • 다음에 select * from Team where TEAM_ID = ???

@ManyToOne, @OneToOne은 기본이 즉시로딩이므로 LAZY 설정 필수


영속성 전이(CASCADE)와 고아 객체

영속성 전이 : CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들 때

주의

  • 영속성 전이는 연관관계를 매핑하는 것과 관련이 없다.
  • 단지 영속성 관리를 위한 편리를 제공하는 기능일 뿐이다.

사용

  • @OneToMany(cascade = CascadeType.XXX) : ALL, PERSIST, REMOVE, MERGE, REFRESH, DETACH
  • 하나의 부모가 자식들을 관리할 때 유용. (게시판의 첨부파일 - 한 게시판에서만 관리) 다른 데서도 첨부파일을 관리한다면 사용X
  • 즉, 부모만이 자식을 소유할 때 혹은 부모와 자식의 라이프사이클이 똑같을 때

고아 객체

  • 부모 엔티티와 연관관계가 끊어진 자식 엔티티

고아 객체 제거

  • @OneToMany(orphanRemoval = true/false)

주의

  • 참조가 제거된 엔티티는 다른 곳에서는 참조하지 않는 고아 객체로 인식하고 삭제해야 함
  • 따라서, 참조하는 곳이 하나이고 특정 엔티티가 개인 소유할 때 사용해야 함
  • CascadeType.REMOVE와 같은 방식으로 동작한다.
  • 일대일, 일대다만 사용 가능

생명주기

CascadeType.ALL + orphanRemoval=true

스스로 생명주기를 관리하는 엔티티는 em.persist()와 em.remove()로 영속, 준영속 가능

두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명주기 관리 가능

도메인 주도 설계(DDD)의 Aggregate Root개념 구현시 유용

  • parent : Aggregate Root
  • child : Aggregate Root가 관리하는 엔티티

0개의 댓글