본문 바로가기
Spring JPA

프록시와 연관관계 관리 - 11

by 홍굴이 2021. 7. 13.

프록시

엔티티가 실제 사용될 때까지 데이터베이스 조회를 지연하는 방법을 지연 로딩이라 한다.

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


프록시 기초

엔티티를 실제 사용하고 싶은 지점까지 미루고 싶을 때, EntityManager.getReference(Class<T> entityClass,
Object primaryKey)
를 사용하면 된다.

  1. 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회한다.
  2. 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성 컨텍스트에 실제 엔티티 생성을 요청하는데 이것을 초기화라 한다.
  3. 영속성 컨텍스트는 데이터베이스를 조회해서 실제 엔티티 객체를 생성한다.
  4. 프록시 객체는 생성된 엔티티 객체의 참조를 Member target 멤버변수에 보관한다.
  5. 프록시 객체는 실제 엔티티 객체의 getName()을 호출해서 결과를 반환한다.

특징

  • 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
  • 프록시 객체를 초기화한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. 즉, 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있다.
  • 프록시 객체는 원본 엔티티를 상속받은 객체이므로 타입 체크 시에 주의해서 사용해야 한다.
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 데이터베이스를 조회할 필요가 없으므로 entityManager.getReference()를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.
  • 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다. -> org.hibernate.LazyInitializationException 예외를 발생
  • 엔티티를 프록시로 조회할 때 식별자 값을 파라미터로 전달하므로 식별자 값을 조회하는 ( ex - getId() )를 호출해도 프록시를 초기화 하지 않는다. (단, 엔티티 접근 방식으로 @Access(AccessType.PROPERTY)로 설정해야 한다.)
  • EntityManager.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(Entity entity) -> boolean 반환형으로 프록시 인스턴스 초기화 여부를 알 수 있다.
  • EntityManagerFactory.getPersistenceUnitUtil().isLoaded(Entity entity) -> boolean 반환형으로 프록시 인스턴스 초기화 여부를 알 수 있다.

즉시 로딩 - EAGER LOADING

  • 엔티티를 조회할 때 연관된 엔티티도 함께 조회한다.
  • ex) @ManyToOne(fetch = FetchType.EAGER)
  • 엔티티를 조회할 때 연관된 엔티티도 같이 조회하므로 쿼리를 2번 이상 실행할 것 같지만, 대부분 JPA는 즉시 로딩을 최적화하기 위해 가능하면 조인 쿼리를 사용한다.
  • 조인 쿼리를 사용하여 즉시 로딩할 때 null을 허용하면 외부 조인, null을 허용하지 않으면 내부 조인으로 된다.

지연로딩 - LAZY LOADING

  • 연관된 엔티티를 실제 사용할 때 조회한다.
  • ex) @ManyToOne(fetch = FetchType.LAZY)
  • 엔티티를 조회할 때 연관된 엔티티가 영속성 컨텍스트에 없다면 프록시 객체로 조회하고 이 프록시 객체를 실제 사용할 때까지 데이터 로딩을 미룬다.

JPA 기본 페치 전략

  • @ManyToOne, @OneToOne : 즉시 로딩(FetchType.EAGER)
    • optional = false : 내부 조인
    • optional = true : 외부 조인
  • @OneToMany, @ManyToMany : 지연 로딩(FetchType.LAZY)
    • optional = false : 외부 조인
    • optional = true : 내부 조인

기본적으로 JPA 기본 페치 전략은 연관된 엔티티가 하나면 즉시 로딩을, 컬렉션이면 지연 로딩을 사용한다.

컬렉션을 로딩하는 것은 비용이 많이 들고 잘못하면 너무 많은 데이터를 로딩할 수 있기 때문이다.

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


영속성 전이 : CASCADE

특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들고 싶으면 영속성 전이 기능을 사용하면 된다.

@Entity
public class Member {
	
    	@Id
        @Column(name = "member_id")
        private String id;
        
        private String userName;
        
        @ManyToOne
        @JoinColumn(name = "team_id")
        private Team team;
        
 }
@Entity
public class Team {
	
    @Id
    @Column(name = "team_id")
    private String id;
    
    private String name;
    
    @OneToMany(mappedBy = "team", cascade = CascadeType.PERSIST)
    private List<Member> memberList = new ArrayList<>();
 }

JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태여야 한다.

EntityManager.persist(Member)를 하면 CascadeType.PERSIST 로 인해 자동으로 Team 엔티티도 영속 상태가 된다.

즉, 엔티티를 영속화할 때 연관된 엔티티도 같이 영속화하는 편리함을 제공한다.

 

Cascade 종류

public enum CascadeType {

    /** Cascade all operations */
    ALL,

    /** Cascade persist operation */
    PERSIST,

    /** Cascade merge operation */
    MERGE,

    /** Cascade remove operation */
    REMOVE,

    /** Cascade refresh operation */
    REFRESH,

    /**
     * Cascade detach operation
     *
     * @since Java Persistence 2.0
     *
     */
    DETACH
}

고아 객체

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능을 제공하는데 이것을 고아 객체(ORPHAN) 제거라 한다.

부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제된다.

@Entity
public class Parent {

    @Id
    @GenerationValue
    private Long id;
    
    @OneToMany(mappedBy = "parent", orphanRemoval = true)
    private List<Child> children = new ArrayList<Child>();
    
}

참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다.

만약 삭제한 엔티티를 다른 곳에서도 참조한다면 문제가 발생할 수 있으므로 @OneToOne, @OneToMany에만 사용할 수 있다. 

'Spring JPA' 카테고리의 다른 글

객체지향 쿼리 언어 - 13  (0) 2021.07.17
값 타입 - 12  (0) 2021.07.16
고급 매핑 - 10  (0) 2021.07.12
다양한 연관관계 매핑 - 9  (0) 2021.07.09
연관관계 매핑 기초 - 8  (0) 2021.07.06

댓글