#08 프록시와 연관관계 관리

김영한 저 "자바 ORM 표준 JPA 프로그래밍, 2015"을 읽고 정리한 내용입니다.

  • 프록시와 즉시로딩,지연로딩

    • 객체가 데이터베이스에 저장되어 있으므로 연관된 객체를 마음껏 탐색하기 어렵다

    • 프록시라는 기술을 사용

    • 실제 사용하는 시점에 데이터베이스에서 조회

  • 영속성 전이와 고아 객체

    • 연관된 객체를 함께 저장, 삭제할 수 있는 영속성 전이(transitive persstence)와 고아 객체 제거

8.1 프록시

엔티티를 조회할 때 연관된 엔티티들이 항상 사용되는 것은 아니다. 연관관계의 엔티티는 비즈니스 로직에 따라 사용될 때도 있지만 그렇지 않을 때도 있다.

// CASE 1. Member, Team 객체 조회 필요
public void printUserAndTeam(String memberId) {
    Member member = em.find(Member.class, memberId);
    Team team = member.getTeam();
    System.out.println("회원 이름: " + member.getUsername());
    System.out.println("소식팀: " + team.getName()); // team 객체 조회
}

// CASE 2. Member 객체 조회 필요
public void printUser(String memberId) {
    Member member = em.find(Member.class, memberId);
    Team team = member.getTeam();
    System.out.println("회원 이름: " + member.getUsername());
}
  • team.getName() 실제 사용하는 시점에 데이터를 조회

  • 지연 로딩 기능을 사용하려면, 실제 엔티티 객체 대신에 데이터베이스 조회를 지연할 수 있는 가짜 객체가 필요한데 이것을 프록시 객체라 한다

  • 지연로딩 방법으로 프록시 말고 바이트코드를 수정하는 방법이 있으나 복

8.1.1 프록시 기초

  • 프록시의 특징

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

    • 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니

      다. 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있다.

    • 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시 주의

    • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.

    • 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다.

준영속상태 초기화

  • 영속성 컨텍스트를 종료해서 member는 준영속 상태. 예외발생

8.1.2 프록시와 식별자 - 어렵다 나중에 다시보기 294p

프록시 객체는 식별자 값을 가지고 있으므로 식별자 값을 조회하는 team.getId()를 호출해도 프록시를 초기화하지 않는다. 단, @Access(AccessType.PROPERTY)로 설정 한 경우에 만 초기하지 않는다. @Access(AccessType.FIELD)는 다른 필드까지 활용해서 어떤 일을 하는 메소 드인지 알지 못하므로 프록시 객체를 초기화한다.

8.1.3 프록시 확인

  • PersistenceUnitUtil().isLoaded(entity)

    • 프록시 인스턴스의 초기화 여부를 확인

  • 진짜 엔티티인지 프록시로 조회한 것인지 확인 _javassist_

JPA 표준에는 프록시 강제 초기화 메소드가 없다. 따라서 강제로 초기화하려면 member.getName()처럼 프록시의 메소드를 직접 호출하면 된다. JPA 표준은 단지 초기화 여부만 확인 할 수 있다.

8.2 즉시 로딩과 지연 로딩

  • 즉시 로딩 : 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.

    • 설정 방법 : @ManyToOne(fetch = FetchType.EAGER)

  • 지연 로딩 : 연관된 엔티티를 실제 사용할 때 조회한다.

    • 설정 방법 : @ManyToOne(getch = FetchType.LAZY)

8.2.1 즉시 로딩

fetch = FetchType.EAGER

  • 회원과 팀 조회

  • 외부 조인 (LEFT OUTER JOIN)을 사용한 것을 유심히 봐야 한다.

  • 외부 조인보다 내부 조인이 성능과 최적화에서 더 유리하다.

  • 내부조인 사용 - 외래 키에 NOT NULL 제약조건을 설정

8.2.2 지연 로딩

fetch = FetchType.LAZY

8.3 지연로딩 활용

8.3.1프로식와 컬렉션 매퍼

  • 엔티티를 영속 상태로 만들 때 엔티티에 컬렉션이 있으면 컬렉션을 추적하고 관리할 목적으로 원본 컬렉션을 내장 컬렉션으로 변경하는데, 이것을 컬렉션 래퍼라 한다.

  • member.getOrders() 컬렉션은 초기화 x

  • 초기화란 영속성컨택스트에 DB조회하라 요청하는것

  • member.getOrders().get(0)이때는 초기화

8.3.2 JPA 기본 페치 전략

  • @ManyToOne, @OneToOne: 즉시 로딩(FetchType.EAGER)

  • @OneToMany, @ManyToMany: 지연 로딩(FetchType.LAZY)

추천하는 방법은 모든 연관관계에 지연 로딩을 사용하고 개발이 어느 정도 완료단계에 왔을 때 상황 보고 필요 한 곳에만 즉시 로딩을 사용하도록 최적화하면 된다.

8.3.3 컬렉션에 FetchType.EAGER 사용 시 주의점

  • 컬렉션을 하나 이상 즉시 로딩하는 것은 권장하지 않는다.

    • 예를 들어, A 테이블을 N, M 두 테이블과 일대다 조인하면 SQL 실행 결과가 N 곱하기 M이 되면서 너무 많은 데이터를 반환할 수 있고 결과적으로 애플리케이션 성능이 저하될 수 있다. 따라서 2개 이상의 컬렉션을 즉시 로딩으로 설정하는 것은 권장하지 않는다.

  • 컬렉션 즉시 로딩은 항상 외부 조인을 사용한다.

    • 데이터베이스 제약조건으로 내부 조인으로 인해 검색이 되지 않는 상황을 막을 수는 없다. 따라서 JPA는 일대다 관계를 즉시 로딩할 때 항상 외부 조인을 사용한다.

FetchType.EAGER 설정과 조인 전략

  • @ManyToOne, @OneToOne

    • (optional = false) : 내부 조인

    • (optional = true) : 외부 조인

  • @OneToMany, @ManyToMany

    • (optional = false) : 외부 조인

    • (optional = true) : 외부 조인

8.4 영속성 전이: CASCADE

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이(transitive persistence) 기능을 사용하면 된다. JPA는 CASCADE 옵션으로 영속성 전이를 제공한다.

@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)

8.4.1 저장,삭제

  • 부모만 영속화하면 CascadeType.PERSIST로 설정한 자식 엔티티까지 함께 영속화해서 저장한다.

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

8.4.2 CASCADE의 종류

PERSIST, MREOMOVEem.persist(), em.remove()를 실행할 때 바로 전이가 발생하지 않고 플러시를 호출할 때 전이가 발생한다.

8.5 고아 객체

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능-

orphanRemoval = true

  • 고아 객체 제거 기능은 영속성 컨텍스트를 플러시할 때 적용된

  • 참조하는 곳이 하나일 때만 사용한. 쉽게 이야기해서 특정 엔티티가 개인소유 하는 엔티티에만 이 기능을 적용해야 한다. 이런 이유로 orphanRemovel은 @OneToOne, @OneToMany에만 사용한다.

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

CascadeType.ALL + orphanRemoval = true를 동시에 사용하면 어떻게 될까?

일반적으로 엔티티는 EntityManager.persist()를 통해 영속화되고 EntityManager.remove()를 통해 제거된다. 이것은 엔티티 스스로 생명주기를 관리한다는 뜻이다. 그런데 두 옵션을 모두 활성화하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있다.

영속성 전이는 DDD의 Aggregate Root개념을 구현할 때 사용하면 편리하다

정리

  • JPA 구현체들은 객체 그래프를 마음껏 탐색할 수 있도록 지원하는데 이때 프록시 기술을 사용한다.

  • 객체를 조회할 때 연관된 객체를 즉시 로딩하는 방법을 즉시 로딩이라 하고, 연관된 객체를 지연해서 로딩하는 방법을 지연 로딩이라 한다.

  • 객체를 저장하거나 삭제할 때 연관된 객체도 함께 저장하거나 삭제할 수 있는데 이것을 영속성 전이라 한다.

  • 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하려면 고아 객체 제거 기능을 사용하면 된다.

[실전 예제]

  • 모두 지연로딩으로 설정

    • @ManyToOne (fetch = FetchType.LAZY)

    • @OneToOne(fetch = FetchType.LAZY)

  • Transitive persistence 설정

    • @OneToOne( ... , cascade = CascadeType.ALL)

    • @OneToMany( ... , cascade = CascadeType.ALL)

Last updated