nathan_H

[JPA] Proxy & CASCADE 본문

Web/Spring

[JPA] Proxy & CASCADE

nathan_H 2020. 3. 25. 22:43

Entity 조회


Spring JPA 를 사용할시 Entity를 가져오는 방법은 크게 두가지이다.

  1. EntityManager.find(target) 메소드를 통한 영속성 컨택스트의 1차 캐시 혹은 데이터 베이스에서 가져온 2차 캐시를 통한 실제 엔티티 조회
  2. EntityManager.getReference(target) 메소드를 통한 Proxy 엔티티를 조회.

1번 같은 경우에는 아마 JPA를 사용해본 경험이 있으면, 익숙하게 다가오고 많이 사용 해봤을것이다. 하지만 2번의 경우는 약간은 생소하고 Proxy 라는 개념 자체도 처음 듣는 사람도 있을 것이다.

여기서 Proxy 라 함은 네트워크 서비스에서 간접적으로 대리 응답을 해줄때 사용하는 개념과 비슷하다고 보면 된다. 즉 Proxy는 실제 엔티티를 조회하는 것이 아니라 가짜 엔티티를 조회한 후 "실제 사용되는 시점"에서 진짜 객체를 조회한다.

그렇다면 굳이 왜 JPA 에는 Proxy 라는 개념을 사용해 가짜 객체를 조회하는 매커니즘이 있는 것일까?

프록시 특징


https://www.baeldung.com/spring-framework-design-patterns

em.find() vs em.getReference() 

 

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

 

  • 프록시를 사용하는 이유에 대해 살펴보기 전에 프록시가 어떤 특징을 가지고 있는지 알아보면 다음과 같다.
  1. 실제 클래스를 상속 받아서 만들어지고 실제 클래스와 겉 모양이 같다. 하지만 실제 엔티티 객체는 아니다.

  2. 사용하는 입장에서는 (이론상으로) 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다. 그리고 프록시 객체는 실제 객체의 참조(target)를 보관.

  3. 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

 

Box proxyBox = entityManager.getReference(Box.class, 1L); // 이 시점에는 실제 엔티티 x 
proxyBox.getCategory(); // 이때 실제 객체 호출

 

 

프록시 객체의 초기화


  • 프록시 객체는 실제 엔티티를 가지고 있지 않은 상태이기 때문에 초기화 과정을 통해 실제 엔티티를 조회한 후 사용이 가능하다.
  • 그리고 초기화 특징은 다음과 같다.
  1. 프록시 객체는 처음 사용할때 한번만 초기화
    • 처음에 값이 없을때 초기화 요청을 함
  2. 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능 → 프록시 객체가 바뀌는게 아니라 내부 타겟 값만 채워지는 것!
  3. 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비교 실패, 대신 instance of 사용)
    • 타입 비교시 웬만해서는 instance of 를 사용해야함 내가 언제 프록시를 사용할지 모르니
  4. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티 반환
    • 프록시로 조회와 상관 없이 영속성 컨텍스트에서 같은 값 조회하면 == 비교가 가능해야함.
      • 프록시로 반환해봐야 이점이 없음. 이미 컨텍스트에 있기 때문에
    • 프록시로 먼저 반환하면 뒤에 값은 값으로 조회한다면 해당 값도 프록시로 반환해서 == 비교가 가능하도록 한다.
    • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생 (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)

그렇다면 이제 프록시의 특징을 살펴봤으니 프록시가 왜 JPA에 사용되고 어떤 이점을 가져다주는지 알아보자.

 

 

즉시 로딩과 지연 로딩


https://terasolunaorg.github.io/guideline/5.0.2.RELEASE/en/ArchitectureInDetail/DataAccessJpa.html

 

  • 스프링 JPA에는 지연 로딩, 즉시 로딩이라는 개념이 있는데, 여기서 지연로딩이 프록시를 활용해 실행되는 기능이다.

 

 

지연로딩


 

  • 지연 로딩이란 자신과 연관된 엔티티를 실제로 사용할 때 연관된 엔티티를 조회 하는 것을 의미.

    • 위 예시에서 팀은 프록시로 가져온 후에 실제 팀을 터치하는 시점에 프록시 객체가 초기화 되고 값을 가져온다고 이해하면 된다.
  • 그리고 JPA는 엔티티에서 매핑시 @ManyToOne(fetch = FetchType.LAZY)을 사용해 지연로딩을 사용.

 

@Entity
@RequiredArgsConstructor
public class Label extends DateTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
    @GenericGenerator(name = "native", strategy = "native")
    @Column(name = "label_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "label_user", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private User user;

    @Column(name = "label_title", length = 10, nullable = false)
    private String title;

    @Column(name = "label_color", length = 7, nullable = false)
    private String color;

 

즉시 로딩


@Entity
@RequiredArgsConstructor
public class Label extends DateTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
    @GenericGenerator(name = "native", strategy = "native")
    @Column(name = "label_id")
    private Long id;

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "label_user", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private User user;

    @Column(name = "label_title", length = 10, nullable = false)
    private String title;

    @Column(name = "label_color", length = 7, nullable = false)
    private String color;
  • 어떤 엔티티를 조회하는데 그 엔티티와 관련된 모든 엔티티들이 함께 조회.
  • 비즈니스 로직이 멤버와 팀이 같이 한번에 조회하는 부분이 많다면 EAGER 을 사용.
    • 즉시 로딩을 사용해서 함께 한번에 조회
  • JPA 구현체는 가능하면 조인을 사용해서 SQL 한번에 함께 조회

프록시와 즉시 로딩 주의 사항 및 전략


  • 가급적 지연 로딩만 사용하는 것을 권장한다.
    • 특히 실무에서 - 김영한님의 강의 중 말씀.
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생함.
    • 조인시 여러개의 테이블이 관계가 걸린 테이블이라면 엄청난 양의 쿼리문이 나감.
      • 조인이 엄청 크게 나간다.
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
    • JPQL 로 조회시 SQL로 자동으로 번역되는데 즉시 로딩으로 되어 있으면 해당 즉시로딩으로 연결되어 있는 값들이 모두 조회가 된다. → N(관계가 연결된 개수) + 1 문제 발생
    • 해결 방법
      • 페치 조인을 활용해서 필요한 경우에만 같이 조회하는 형식으로 함.
      • 엔티티 그래프 어노테이션
      • 배치 사이즈
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정
  • @OneToMany, @ManyToMany는 기본이 지연 로딩

영속성 전이와 고아 객체


영속성 전이


https://www.slideshare.net/caroljmcdonald/td09jpabestpractices2

  • 특정 엔티티를 영속 상태로 만들 때 연관된 엔티티도 함께 영속 상태로 만들도 싶을 때
    • 예: 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장.

영속성 전이 : 주의 사항


  • 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
  • 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐
  • Practice
    • 부모 객체가 자식 객체를 일괄적으로 관리할때.
      • 그러나 자식 객체가 다른 관계랑 또 연관되어 있으면 안됨.
    • 소유자가 한명일 경우에만

CASCADE의 종류


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

고아 객체


  • 고아 객체 제거: 부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제한다.
  • orphanRemoval = true

고아 객체 주의


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

영속성전이+고아객체을 활용한 생명주기 관리


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

[Spring JPA] 프록시( proxy )와 지연로딩

 

[Spring JPA] 프록시( proxy )와 지연로딩

지연로딩이 필요한 이유와 프록시( proxy ) 지연 로딩이란 자신과 연관된 엔티티를 실제로 사용할 때 연관된 엔티티를 조회( SELECCT )하는 것을 말합니다. 반대로 즉시 로딩이란 엔티티를 조회할 때 자신과 연관..

victorydntmd.tistory.com

How does a JPA Proxy work and how to unproxy it with Hibernate - Vlad Mihalcea

 

How does a JPA Proxy work and how to unproxy it with Hibernate - Vlad Mihalcea

Introduction The JPA lazy loading mechanism can either be implemented using Proxies or Bytecode Enhancement so that calls to lazy associations can be intercepted and relationships initialized prior to returning the result back to the caller. Initially, in

vladmihalcea.com

5.3. Database Access (JPA) - TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.0.2.RELEASE documentation

 

5.3. Database Access (JPA) — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.0.2.RELEASE documentation

To add query method to Repository for searching the entities as per dynamic conditions, search process should be implemented by creating custom Repository interface and custom Repository class for the entity specific Repository interface. For method of cre

terasolunaorg.github.io

Design Patterns in the Spring Framework | Baeldung

 

Design Patterns in the Spring Framework | Baeldung

Learn about four of the most common design patterns used in the Spring Framework

www.baeldung.com

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런

JPA Best Practices

 

JPA Best Practices

Java Persistence API Best Practices for concurrency, caching, database

www.slideshare.net

 

'Web > Spring' 카테고리의 다른 글

[JPA] 영속성 컨텍스트 란?  (0) 2020.03.23
Comments