LostCatBox

(JPA) JPA-Basic-CH07(프록시)

Word count: 583Reading time: 3 min
2023/04/18 32 Share

프록시

왜?

(Member : Team = N : 1)Member를 조회할때 Team도 함께 조회해야 할까?

비즈니스 로직에 따라다르다.

  • 거의 항상 Member를 불러낸후, team의 정보와 같이 출력하는 비즈니스 로직있다면,
    Team정보까지 쿼리 한번에 가져오는 것이 좋다
  • Member만 쓰고, Team을 가끔 쓰는경우,
    Member만 쿼리로 호출하는것이 좋음
  • JPA에서는 즉시 로딩과 지연 로딩을 활용하여, 문제해결

프록시 기초

  • em.find() vs em.getReference()
  • find()는 진짜 엔티티 객체를 반환함
  • getReference()는 가짜 엔티티 객체를 반환함. => Proxy로 싸여져있고, 들어가있는 구성만 동일(값은없음)
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

프록시 특징

스크린샷 2023-04-18 오후 11.25.12

스크린샷 2023-04-18 오후 11.25.46

프록시 객체의 초기화

  • MemberProxy에 Member target에 값이없다면, 초기화를 요청함
  • 즉, 프록시에 값을 실제 사용할때, 그때 프록시 값이 없을시, 영속성 컨텍스트로 DB조회가 일어남

스크린샷 2023-04-18 오후 11.26.43

프록시의 특징(!!!)

  • 프록시객체는 처음 사용할때 한번만 초기화

  • 프록시객체를 초기화할 때,프록시객체가 실제엔티티로 바뀌는것은 아님,
    초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능

  • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함
    (== 비교 실패, 대신 instance of 사용)

    • member1, member2 의 타입비교시,
    • member1.getClass() ==member2.getClass() 하면안됨!
    • (member1 instanceof Member) 사용해야함로 해야함( 비교함수를 만들때도, 타입체크시 instanceof로 사용하자)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면
    em.getReference()를 호출해도 실제 엔티티 반환

    • JPA에서는 한 트랜잭션 안에서 같은 것을 호출한 모든 것은 같음(==) 통해야함(원칙)

    • Member reMember = em. getReference (Member. class, member1. get Id());
      System.out.printin("refMember = " + refMember.getClass ()) ; //Proxy
      Member findMember = em. find (Member.class, member1. getId());
      System.out.println("findMember = " + findMember.getClass ()) ; //Member
      System.out •println("refMember == findMember: " + (refMember == findMember)) ;
      <!--0-->
      

지연 로딩

스크린샷 2023-04-19 오후 11.43.26

즉시 로딩 EAGER를 사용해서 함께 조회

  • Join으로 한번에 다 가져옴
1
2
3
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "team")
private Team team;

프록시와 즉시로딩 주의(!!!)

  • 가급적 지연 로딩만 사용(특히 실무에서)
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다
    (JPQL로 Member조회시 Member의 row수(N)만큼 Team select 쿼리갯수 더나감)
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정
  • @OneToMany, @ManyToMany는 기본이 지연 로딩

지연 로딩 활용

  • 모든 연관관계에 지연 로딩을 사용해라!
  • 실무에서 즉시 로딩을 사용하지 마라!
  • JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라!
 (뒤에서 설명)
  • 즉시 로딩은 상상하지 못한 쿼리가 나간다.

영속성 전이: CASCADE

참조

cascade-persist

기본 특징

  • 특정 엔티티를 영속 상태로 만들 떄 연관된 엔티티도 함께 영속상태로 만들고싶을떄
  • 예: 부모 엔티티를 저장할 떄 자식 엔티티도 함께 저장, 자동으로 다같이 PERSIST로 영속상태로 만들고싶을때!
  • 연관관계, 즉시로딩, 지연로딩과 상관없음!

스크린샷 2023-04-20 오후 10.29.20

영속성 전이: CASCADE - 주의

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

CASCADE의 종류

  • ALL : 모두 적용 (모든 라이프사이클 같다)
  • PERSIST: 영속
    • 저장할 때만 씀!
    • 단일 소유자일때만 사용해야한다.
    • 단일 엔티티에 완전 종속적
  • REMOVE: 삭제
  • MERGE: 병합
  • REFRESH : 리프레쉬
  • DETACH: DETACH

고아 객체

  • 고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
  • orphanRemoval = true
1
2
3
4
5
Parent parent1 = em.find(Parent.class, id)
parent1.getChildren().remove(0)

>>>
DELETE FROM CHILD WHERE ID = ?

고아 객체 - 주의

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

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

  • CascadeType.ALL + orphanRemovel = true
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
  • 두 옵션을 모두 활성화 하면, 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있음
  • 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용
CATALOG
  1. 1. 프록시
  2. 2. 지연 로딩 활용
  3. 3. 영속성 전이: CASCADE
  4. 4. 고아 객체
  5. 5. 영속성 전이 + 고아 객체, 생명주기