220927_TIL : 자바 ORM 표준 JPA 프로그래밍 - 기본편

백승한·2022년 9월 28일
0

스프링

목록 보기
11/14

프록시

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

프록시 특징

  • 실제 클래스를 상속 받아서 만들어짐
  • 실제 클래스와 겉 모양이 같다.
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
  • 프록시 객체는 실제 객체의 참조(target)를 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출 (target에 있는 getName()을 대신 호출, 물론 처음에는 DB 조회를 안했기때문에 target값이 없다)

프록시 객체의 초기화


member는 프록시 객체이다.
DB조회를 하고 실제 Entity와 Member target을 연결시켜준다.

프록시의 특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("before findMember = " + findMember.getClass()); // 둘이 동일
// 초기화를 통해 실제 엔티티에 접근해서 getName()을 가져온다.
System.out.println("findMember.username = " + findMember.getName());
System.out.println("after findMember = " + findMember.getClass()); // 둘이 동일
  • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비
    교 실패, 대신 instance of 사용)
reference instanceof Member
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해
    도 실제 엔티티 반환
    , 그 반대도 마찬가지이다. 이미 처음에 프록시로 조회를 해버리면, em.find()를 해서 출력해도 프록시 객체가 출력된다. JPA는 이게 제일 중요하다. 즉, JPA는 프록시든 아니든 같은 트랙잭션 안에서는 == 비교가 true가 되게끔 만든다.
Member m1 = em.find(Member.class, member.getId());
System.out.println("m1 = " + m1.getClass());

Member findMember = em.getReference(Member.class, member.getId());
// em.find()를 통해 실제 엔티티를 가져왔기때문에 프록시가 아닌 실제 엔티티 출력
System.out.println("findMember = " + findMember.getClass());
  • !!! 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면
    문제 발생 !!!

    (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)

프록시 확인

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

em.getReference()를 많이 쓰지않는데 왜 이렇게 공부하냐 ?
다음에 배울 즉시 로딩과 지연 로딩을 깊이있게 이해하기위한 바탕이다.

즉시 로딩과 지연 로딩

지연 로딩 LAZY을 사용해서 프록시로 조회

Team team = member.getTeam();
team.getName(); // 실제 team을 사용하는 시점에 초기화(DB 조회)

즉시 로딩(EAGER), Member조회시 항상 Team도 조회

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

프록시와 즉시로딩 주의

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

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

영속성 전이: CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들도 싶을 때
  • 예: 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장.

영속성 전이: CASCADE - 주의!

  • 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐
  • 라이플 사이클 연관관계와 완전하게 맞추고 싶다면 ALL,
    저장할때만 사이클을 맞추고 나머지는 따로 관리하고 싶다면 PERSIST를 하면 된다.
  • 보통 소유자가 하나 일때 사용한다. 너무 여기저기 묶여있을때 사용하면 큰일난다.

고아 객체

  • 고아가 되면 자동 삭제
  • 고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
  • orphanRemoval = true
  • Parent parent1 = em.find(Parent.class, id);
    parent1.getChildren().remove(0);
    //자식 엔티티를 컬렉션에서 제거

고아 객체 - 주의

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

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

  • CascadeType.ALL + orphanRemovel=true
  • 스스로 생명주기를 관리하는 엔티티는 em.persist()로 영속화, em.remove()로 제거
  • 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음 -> 이럴때는 자식의 Repo는 만들지 않아도된다.
  • 도메인 주도 설계(DDD)의 Aggregate Root개념을 구현할 때 유용
profile
방문해주셔서 감사합니다🙂

0개의 댓글