본문 바로가기

STUDY/JPA

[자바 ORM 표준 JPA 프로그래밍 - 기본편] 프록시와 연관관계 관리

인프런 - 자바 ORM 표준 JPA 프로그래밍 (김영한님) 

 

프록시

  • em.find() vs em.getReference()
    • em.find() : 데이터베이스를 통해 실제 엔티티 객체 조회
    • em.getReference() : 데이터베이스 조회가 아닌 가짜(프록시) 엔티티 객체 조회 
Member member = new Member();
member.setName("test1");
em.persist(member);
em.flush();
em.clear();

// findMember = Proxy 클래스
Member findMember = em.getReference(Member.class, member.getId());

// member.getId()는 이미 알고 있는 값이므로 DB 조회하지 않음
System.out.println(findMember.getId());

 

 

프록시 특징

  • 실제 클래스를 상속 받아서 만들어짐
  • 실제 클래스와 겉 모양이 같음
  • 프록시 객체는 실제 객체의 참조(target)을 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출

 

 

프록시 객체의 초기화

Member member = em.getReference(Member.class, "id1");
member.getName();

 

  • member는 프록시 객체
  • member.getName() 호출 시 MemberProxy의 target은 null 상태 
  • 영속성 컨텍스트에 초기화 요청
  • DB에서 Member를 조회해 반환
  • MemberProxy의 target에 조회된 실제 Entity를 연결 후 target.getName()으로 반환

 

 

프록시의 특징

  • 프록시 객체는 처음 사용할 때 한번만 초기화
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님
    • 초기화 되면 프록시 객체를 통해 실제 엔티티에 접근 가능
  • 프록시 객체는 원본 엔티티를 상속 받음
    • 타입 체크시 주의 (== 비교 대신 instance of 사용)
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환 
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
    • LazyInitializationException 발생

 

영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환

Member m1 = em.find(Member.class, member1.getId());
System.out.println(m1.getClass());

// class hellojpa.Member

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

// class hellojpa.Member

/**
 * em.find 했을 때 영속성 컨텍스트에 올라간 상태
 * JPA에서는 한 영속성 컨텍스트에서 가져온 것은 == 비교를 했을 때 true 
 */

 

Member ref1 = em.getReference(Member.class, member1.getId());
Member ref2 = em.getReference(Member.class, member1.getId());
System.out.println(ref1 == ref2)

// true


Member ref1 = em.getReference(Member.class, member1.getId());
Member m1 = em.find(Member.class, member1.getId());
System.out.println(ref1 == m1);

// true 

/**
 * m1은 Proxy 객체가 반환되므로 true
 */

 

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인
PersistenceUnitUtil.isLoaded(Object entity);

 

  • 프록시 클래스 확인 방법
entity.getClass().getName();

 

  • 프록시 강제 초기화
org.hibernate.Hibernate.initialize(entity);

 

  • 참고: JPA 표준은 강제 초기화 없음 
  • 강제 호출 : member.getName();

 

 

즉시 로딩과 지연 로딩

지연 로딩

@Entity
public class Member {
	@Id @GeneratedValue
    private Long id;
    
    private String name;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name  "TEAM_ID")
    private Team team;
}

Member member = em.find(Member.class, 1L);
// team은 프록시 객체
Team team = member.getTeam();

// 실제 team을 조회하는 시점에 초기화
team.getName();

 

  • FetchType.LAZY : Member 클래스만 조회, Team은 프록시로 조회
  • member.getTeam(); 까지는 프록시 객체만 가져오므로 조회 쿼리가 나가지 않음
  • team.getName(); 으로 실제 team을 조회 시 DB에서 조회

 

즉시로딩

@Entity
public class Member {
	@Id @GeneratedValue
    private Long id;
    
    private String name;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name  "TEAM_ID")
    private Team team;
}

// member 조회 시 이미 Team도 조회됨
Member member = em.find(Member.class, 1L);
Team team = member.getTeam();
team.getName();

 

  • Member와 Team을 자주 함께 사용한다면 즉시 로딩 사용
  • JPA 구현체는 가능하면 조인을 사용해 SQL 한번에 함께 조회

 

 

프록시와 즉시 로딩 주의

  • 가급적 지연 로딩만 사용 (특히 실무에서)
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생할 수 있음 
  • 즉시 로딩은 JPQL에서 N+1 문제가 발생
    • 1을 조회하기 위해 N개의 쿼리가 추가 실행되는 문제
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 LAZY로 설정 필요!! 
  • @OneToMany, @ManyToMany는 기본이 지연 로딩

 

 

 

영속성 전이 : CASCADE

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때
  • 즉시로딩, 지연로딩과 관계 X
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐 그 이상도 이하도 아님
  • 예 ) 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장

 

CASCADE 종류

  • ALL: 모두 적용
  • PERSIST: 영속(저장할 때만)
  • REMOVE: 삭제
@OneToMany(mappedBy="parent", cascade=CascadeType.ALL)

 

 

고아객체

  • 고아객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
  • orphanRemoval = true로 설정했을 떄
Parent parent = em.find(Parent.class, parent.getId());
// remove(0)을 수행하면 orphanRemoval가 실행됨
parent.getChildList().remove(0);

 

 

고아객체 주의

  • 참조하는 곳이 하나일 때 사용
  • 특정 엔티티가 개인 소유할 때 사용
  • 즉, @OneToOne, @OneToMany만 가능

 

 

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

  • CascadeType.ALL + orphanRemovel = true, 두 옵션을 모두 활성화 하면 부모 엔티티를 통해 자식의 생명 주기를 관리할 수 있음
    • 자식의 Repository를 만들지 않아도 됨
  • 도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용