[SpringBoot] 프록시와 연관관계 관리

·2023년 9월 9일
post-thumbnail

프록시

Member와 Team이 있을 때, Member를 조회할 때, Team이 필요하지 않을 때가 있다.

em.find() vs em.getReference() → 참조 가져오기

em.find()는 데이터베이스를 통해서 실제 엔티티 객체를 조회해오는 거라면, em.gerReference()는 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체를 조회하는 것이다 그래서 DB에 쿼리가 나가지 않아도 조회를 해올 수 있게 된다.

프록시 특징

  • 실제 클래스를 상속 받아서 만들어진다.
  • 실제 클래스와 겉 모양이 같다.
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체이지 구분하지 않고 사용하면 된다.(이론상)
  • 프록시 객체는 실제 객체의 참조(target)를 보관한다.
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.

초기화가 되면 멤버 프록시에 name이 없으면 영속성 컨텍스트로 요청을 보내게 되고 받아와서 실제 entity 생성을 하여 프록시와 연결을 하게 된다.

개발자는 객체가 프록시로 넘어 올지 실제로 넘어올지 일일이 확인하기 어려우므로 ==비고 대신 instance of를 사용하자. 영속성 컨택스트에 찾는 엔티티가 이미 있으면 em.getReference()를 프록시가 아닌 실제가 반환된다.(==을 맞추기 위해) 영속성 컨텍스트에서 벗어나 준영속상태나 em.clear()를 통해 영속성 컨택스트가 초기화된 경우에는 문제가 생긴다.(프록시 초기화는 영속성 컨택스트에서 다루기 때문)

프록시를 확인하는 방법

  • 프록시 인스턴스의 초기화 여부 확인
    • PersistenceUnitUtil.Loaded(Object entity)
  • 프록시 클래스 확인 방법
    • entity.getClass().getName() 출력(..javasist.. or HibernateProxy..)
  • 프록시 강제 초기화
    • org.hibernate.Hibernate.initialize(entity);
  • 참고: JPA표준은 강체 초기화 없음
    • 강제 호출: member.getName()

즉시 로딩과 지연 로딩

앞에서와 마찬가지로Member를 조회할 때 Team도 함께 조회를 하지 않아도 될때가 있다.

지연 로딩 LAZY를 사용해서 연관관계를 프록시로 가져올 수 있다. → (fetch = FetchType.LAZY)

지연 로딩을 사용하여 프록시로 조회를 하고 실제 team을 사용하는 시점에 초기화를 하여 효율적으로 사용할 수 있다.

만약, 프로젝트의 대부분이 Member와 Team을 함께 사용한다면 즉시 로딩 EAGER를 사용해서 함께 조회를 할 수 있다.

JPA 구현체는 가능하면 조인을 사용해서 SQL 한번에 함께 조회한다.

하지만 즉시 로딩을 사용할 때는 주의할 점이 있다.

  • 가급적 지연 로딩만 사용(특히 실무에서)
  • 즉시 로딩을 사용하면 여러개가 같이 걸려있을 때 Join과 같은 예상하지 못한 SQL이 발생 할 수 있다.
  • 즉시 로딩은 쿼리를 하나 날렸는데 추가 쿼리가 N개가 발생할 수 있어 JPQL에서 N+1의 문제를 일으킨다.
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩 → LAZY로 설정
  • @OneToMany, @ManyToMany는 기본이 지연 로딩

결론은 테이블이 몇개 없는 간단한 프로젝트가 아닌 이상 무조건 지연 로딩을 사용하자!

JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라!

영속성 전이: CASCADE

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

ex) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장.

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

하나의 부모가 자식들을 다 관리할 때 쓰기 좋지만 자식이 또다른 곳과 연관되어 있으면 문제가 생길 수 있다. → 소유주가 하나일때 + 라이프 사이클이 비슷할때만 사용하자!

CASCADE의 종류

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

고아 객체

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

Parent parent1 = em.find(Parent.class, id);
parent1.getChildren().remove(0);
//자식 엔티티를 컬렉션에서 제거

DELETE FROM CHILD WHERE ID=?

주의

  • 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능
  • 참조하는 곳이 하나일 때 사용해야함!
  • 특정 엔티티가 개인 소유할 때 사용
  • @OneToOne, @OneToMany만 가능
  • 참고: 개념적으로 부모를 제거하면 자식은 고아가 된다. 따라서 고아 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거된다. 이것은 CascadeType.REMOVE처럼 동작한다.

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

  • CascadeType.ALL + orphanRemoval=true
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
  • 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음
  • 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용
profile
고민0

0개의 댓글