자바 ORM 표준 JPA 프로그래밍 - 프록시와 연관관계 관리

서은경·2022년 5월 11일
0

Java

목록 보기
11/19

프록시

em.find() vs em.getReference()

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

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

프록시 객체의 초기화


            Member findMember = em.getReference(Member.class, member.getId());
            System.out.println(findMember.getId() + " / " + findMember.getName());
  1. getId() 메소드 호출
  2. 영속성 컨텍스트에 초기화 요청
  3. DB 조회
  4. 실제 Entity 생성
  5. target.getId() 호출

초기화 과정 이후엔 DB 접근 없이 데이터를 가져올 수 있다

프록시의 특징

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

프록시 확인

  1. 프록시 인스턴스의 초기화 여부 확인
    PersistenceUnitUtil.isLoaded(Object entity)
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(findMember));
  1. 프록시 클래스 확인 방법
    entity.getClass().getName() 출력(..javasist.. or HibernateProxy...)
System.out.println(findMember.getClass());
  1. 프록시 강제 초기화
    참고: JPA 표준은 강제 초기화가 없어서 메소드 강제호출로 초기화해줌
    org.hibernate.Hibernate.initialize(entity);
Hibernate.initialize(findMember);

즉시 로딩과 지연 로딩

지연로딩

지연로딩으로 세팅하면 연관된 걸 프록시로 가지고옴

public class Member extends BaseEntity{

    @Id @GeneratedValue
    @Column(name="MEMBER_ID")
    private Long id;

    @Column(name="USERNAME")
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;

즉시로딩

member 객체를 가져올 때 team까지 같이 가져옴

public class Member extends BaseEntity{

    @Id @GeneratedValue
    @Column(name="MEMBER_ID")
    private Long id;

    @Column(name="USERNAME")
    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "TEAM_ID")
    private Team team;

프록시와 즉시로딩 주의

  • 가급적 지연 로딩만 사용 (특히 실무에서)
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다
List<Member> members = em.createQuery("select m from Member", Member.class).getResultList();

// member 쿼리 하나, team 쿼리 하나 총 두번 돔
em.find는 pk를 찍어서 가져오기 때문에 JPA가 내부적으로 최적화 할 수 있지만 JPQL은 그대로 SQL로 번역이 됨
즉시로딩은 무조건 값이 들어가있어야 하기 때문에 별도로 팀을 가져오기 위한 쿼리가 실행됨
  • @ManyToOne, @OneToOne은 기본이 즉시로딩이라 LAZY로 설정해줘야함
  • OneToMany, @ManyToMany(안씀) 은 디폴트가 지연로딩

영속성 전이(CASCADE)

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

Parent

@Entity
public class Parent {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> childList = new ArrayList<>();

Child

@Entity
public class Child {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "parent_id")
    private Parent parent;

CASCADE를 걸어주면 parent 엔티티만 저장해도 child까지 저장됨

주의점

  • 하나의 부모가 자식들을 관리할 땐 유용하지만(ex.parent와 child만 관계를 맺을 때만 사용할 수 있지만 member와 child에도 연관관계가 있으면 쓸 수 없음)
  • 단일 entity에 완전히 종속적일 땐 사용해도 무관(라이프 사이클이 똑같아서)
  • 단일 소유자(=소유자가 하나일때)

종류

  1. ALL : 모두 적용
  2. PERSIST : 영속
  3. REMOVE : 삭제
  4. MERGE : 병합
  5. REFRESH : REFRESH
  6. DETACH : DETACH

고아객체

  • 고아 객체 제거 : 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제
  • orphanRemoval = true
@Entity
public class Parent {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Child> childList = new ArrayList<>();

Parent findParent = em.find(Parent.class, parent.getId());
findParent.getChildList().remove(0);
// 자식 엔티티를 컬렉션에서 제거

주의

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

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

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

0개의 댓글

관련 채용 정보