7. 프록시와 연관관계 관리

1. 프록시

  • em.find() vs em.getReference()
    • em.find(): 데이터 베이스를 통해서 실제 엔티티 객체 조회
    • em.getReference(): 데이터 베이스 조회를 미루는 가짜(프록시) 객체 조회
Member reference = em.getReference(Member.class, 1l); // 실제 호출 되지는 않고 가짜 객체 생성
System.out.println("findMember = " + reference.getUsername()); // 이때 값을 DB에서 가져와야 하기 떄문에 DB 조회
System.out.println("findMember = " + reference.getId());
  • 프록시 특징
    • 프록시 객체는 처음 사용할 때 한번만 초기화
    • 프록시 객체를 초기화할 때 프록시 객체가 실제 엔티티로 바뀌는 것이 아님, 초기화 되면 프록시 객체를 통해 실제 엔티티에 접근 가능
    • 프록시 객체는 원본 엔티티를 상속 받음, 따라서 타입 체크시 주의해야함( == 비교 실패, 대신 instance of 사용)
Member m1 = em.find(Member.class, member1.getId()); // 실제 호출
Member m2 = em.getReference(Member.class, member2.getId()); // 실제 호출
System.out.println("m1 == m2" + (m1.getClass() == m2.getClass())); // false 반환


private static void logic(Member m1, Member m2) {
        System.out.println("m1 == m2" + (m1 instanceof Member)); // true 반환
        System.out.println("m1 == m2" + (m2 instanceof Member)); // true 반환
}
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference를 호출해도 실제 엔티티 반환
Member m1 = em.find(Member.class, member1.getId()); // 실제 호출
System.out.println("m1.getClass() = " + m1.getClass());

Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference.getClass() = " + reference.getClass());

System.out.println("a == a:" + (m1 == reference)); // true 반환

>> 위에서 참조한 값이기 때문에 둘다 프록시객체가 아닌 엔티티 객체로 생성
  • 프록시로 한번 반환을 하면 그 다음 em.find()를 해도 프록시를 반환
Member refMember = em.getReference(Member.class, member1.getId()); 
System.out.println("refMember.getClass() = " + refMember.getClass()); // 프록시 호출

Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember.getClass() = " + findMember.getClass()); // 프록시 반환

System.out.println("a == a:" + (refMember == findMember)); // true 반환
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때 초기화면 문제 발생
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember.getClass() = " + refMember.getClass()); // 프록시 호출

em.detach(refMember); // or em.close()
refMember.getUsername();

>> 에러 발생
  • 프록시 객체 초기화
  • 프록시 확인
    • System.out.println("refMember" = emf.getPersistenceUnitUtil().isLoaded(refMember));
      • 프록시 인스턴스의 초기화 여부 확인
    • System.out.println("refMember =" + refMember.getClass())
      • 프록시 클래스 확인 방법
    • Hibernate.initialize(refMember);
      • 강제 초기화
      • JPA 표준에는 강제 초기화가 없음
    • 실제 프록시의 getReference()는 많이 사용하지 않음

2. 즉시 로딩과 지연 로딩

  • 지연 로딩
@ManyToOne(fetch = FetchType.LAZY) //지연로딩
@JoinColumn(name = "TEAM_ID",insertable = false, updatable = false)
private Team team;

>> 프록시 타입으로 조회
  • 즉시 로딩
@ManyToOne(fetch = FetchType.EAGER) // 즉시로딩
@JoinColumn(name = "TEAM_ID",insertable = false, updatable = false)
private Team team;

>> 즉시 엔티티 객체로 조회
  • 가급적 지연 로딩만 사용
    • 즉시 로딩을 사용하면 연동되어 있는 수십개의 테이블 여러 개를 한번에 가지가옴
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
  • 즉시 로딩은 JPQL에서 n+1 문제를 일으킨다.
  • ManyToOne, OneToOne은 기본이 즉시 로딩으로 되어있다. ->Lazy로 설정

3. 영속성 전이:CASECADE

  • 특정 엔티티를 영속 상태로 만들 떄 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
    • ex) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장
  • 영속성 전이는 연관관계를 저장할 때 자식 엔티티도 함께 저장
  • 종류
    • All
    • PERSIST
    • REMOVE
    • MERGE
    • REFRESH
    • DETACH
  • 게시판 및 첨부 파일 같은 경우 사용 가능 -> 하나의 부모가 모든 자식을 관리 할 때(소유자가 하나)
    • 그러나 그 첨부 파일을 다른 게시판 즉 다른 부모가 관리하면 사용 X

4. 고아 객체

  • 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
  • orphanRemoval = true
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> children = new ArrayList<>();

----------------------------------------------------------
Parent parent1 = em.find(Parent.class, parent.getId());
parent1.getChildren().remove(0);
  • 참조하는 곳이 하나일 때 사용해야함
  • 특정 엔티티가 개인 소유할 때 사용
  • 이는 부모를 제거할 때 자식이 자동적으로 삭제됨
  • CASCADE의 REMOVE 처럼 사용 가능

좋은 웹페이지 즐겨찾기