computer_study

[JPA] 13. 웹 애플리케이션과 영속성 관리 본문

스터디/자바 ORM표준 JPA 프로그래밍

[JPA] 13. 웹 애플리케이션과 영속성 관리

knowable 2022. 10. 4. 02:20

스프링이나 J2EE 컨테이너 환경에서 JPA가 동작하는 동작 방식을 이해한다.

1. 트랜잭션 범위의 영속성 컨텍스트

스프링이나 J2EE컨테이너 환경에서 JPA사용 시 컨테이너가 제공하는 전략을 따라야 한다.

 

스프링 컨테이너의 기본 전략

트랜잭션 범위의 영속성 컨텍스트 전략을 사용. (트랜잭션 시작 시 영속성 컨텍스트 생성, 트랜잭션 끝나면 영속성 컨텍스트 종료)

  • 스프링에서 서비스 계층에 @Transactional을 선언해서 트랜잭션을 시작
  • 이 때 AOP가 먼저 동작하게 된다.
  • 메소드 종료 시 트랜잭션 커밋하며 종료된다. (이 때 JPA는 영속성 컨텍스트를 플러시하여 변경 내용을 DB에 반영)
  • 예외 발생 시 플러시를 호출하지 않는다.
  • 트랜잭션이 같으면 같은 영속성 컨텍스트 사용 (다르면 다른 영속성 컨텍스트 사용 (같은 엔티티 매이저를 사용하더라도))
  • 이런 트랜잭션과 복잡한 멀티 스레드 상황을 컨테이너가 처리해준다.

2. 준영속 상태와 지연 로딩

  • 서비스, 레포지토리 계층이 아닌 컨트롤러나 뷰 같은 프리젠테이션 계층에서는 준영속 상태.
    • 변경 감지 기능이 동작하지 않는다.
      • 프리젠테이션 계층에선 변경이 끝난 이후 보여주는 것에만 집중하면 되므로 동작하지 않아도 별 상관 없다.
    • 지연로딩이 동작하지 않는다
      • 뷰 렌더링 시 연관된 엔티티를 사용하고, 엔티티를 지연 로딩으로 설정해서 조회하는 상황
        영속성 컨텍스트가 없는 준영속 상태에선 지연로딩 시도 시 예외 발생
      • 해결 방법
        • 뷰가 필요한 엔티티를 미리 로딩
        • OSIV를 사용해서 엔티티를 항상 영속 상태로 유지

 

뷰가 필요한 엔티티를 미리 로딩하는 방법

  • 글로벌 페치 전략 수정
    • 글로벌 페치 전략을 지연 로딩에서 즉시 로딩으로 변경
    • @ManyToOne(fetch = FetchType.EAGER) 로 설정 가능
    • 단점
      • 사용하지 않는 엔티티를 로딩
      • N+1문제 발생
        • 연관 관계가 설정된 엔티티를 조회할 경우 조회한 데이터 수 만큼 추가로 SQL을 사용해서 조회하는 문제
        • order와 member가 연관되어있을 때, 조회한 order엔티티가 10개라면 member를 조회하는 SQL도 10번 실행된다
        • JPQL페치 조인으로 해결 가능(15.4.1절)
  • JPQL 페치 조인
    • JPQL을 호출하는 시점에 함께 로딩할 엔티티를 선택하는 방법
    • 조인 명령어 마지막에 fetch를 넣어주면 된다.
    • 단점
      • 특정 화면에 맞춘 레포지토리 메소드가 증가할 수 있다 (프리젠테이션 계층이 접근 계층을 침범)
      • 적절한 타협점을 찾아야 한다.
JPQL:
    select o
    from Order o
    join fetch o.member

SQL:
    select o.*, m.*
    from Order o
    join Member m on o.MEMBER_ID=m.MEMBER_ID
  • 강제로 초기화
    • 영속성 컨텍스트가 살아있을 때 프리젠테이션 계층이 필요한 엔티티를 강제로 초기화해서 반환하는 방법
    • 프록시 객체는 실제 사용하는 시점에 초기화되므로 실제 값을 호출해버린다
      • ex) order.getMember() 까지 하면 프록시 객체만 반환하지만
        order.gerMember().getName()까지 해서 실제 값을 사용하는 시점에 초기화
    • 하이버네이트를 사용하면 initialize()메소드를 사용해서 초기화도 가능
    • 뷰가 필요한 엔티티에 따라 서비스 로직을 변경해야되는 상황(프리젠테이션 계층이 서비스 계층을 침범한 것)
      • 서비스 계층에서 프리젠테이션 계층을 위한 프록시 초기화 역할을 분리해야 한다 (FACADE계층이 담당)
  • FACADE 계층 추가
    • Facade에서 트랜잭션을 시작한다.
    • 역할과 특징
      • 프리젠테이션 계층과 도메인 모델 계층 간의 논리적 의존성 분리
      • 프리젠테이션 계층에서 필요한 프록시 객체 초기화
      • 서비스 계층을 호출해서 비즈니스 로직 실행
      • 리포지토리를 직접 호출해서 뷰가 요구하는 엔티티 찾음
    • 중간 계층이 하나 더 있는 것이기에 더 많은 코드를 작성해야 하는 단점이 있다 (단순히 서비스 계층을 호출하는 코드가 많을 것이다.)

준영속 상태와 지연 로딩의 문제점

위 방법을 사용한다고 해도, 준영속 상태라는 것 때문에 번거로운 것은 사실

영속성 컨텍스트를 뷰까지 열어두는 것이 가장 좋은데 이게 OSIV

 

3. OSIV

  • 영속성 컨텍스트를 뷰까지 열어두는 방법 (뷰에서도 지연 로딩 사용이 가능하다)

  • 기존 OSIV
    • 요청이 들어올 때 영속성 컨텍스트를 생성해 유지하고 요청이 끝나면 트랜잭션도 끝낸다.
  • 기존 OSIV 문제점
    • 컨트롤러나 뷰 같은 프리젠테이션 계층이 엔티티를 변경할 수 있다
    • 변경하지 못하게 하는 방법
      • 엔티티를 읽기 전용 인터페이스로 제공
      • 엔티티 레핑
      • DTO만 반환 (단순히 데이터만 전달하는 객체 DTO를 생성해서 반환)
    • 위 방법 모두 코드량이 상당히 증가하게 된다.
    • 이런 요청당 트랜잭션 방식의 OSIV는 최근 거의 사용되지 않는다
  • 최근에는 비즈니스 계층에서만 트랜잭션을 유지하는 방식의 OSIV를 사용 (스프링 프레임워크가 제공)

  • 특징
    • 요청이 들어올 때 부터 끝날 때 까지 같은 영속성 컨텍스트를 유지 (한 번 조회한 엔티티는 요청이 끝날 때 까지 영속상태 유지)
    • 엔티티 수정은 트랜잭션이 있는 계층에서만 동작.(프리젠테이션 계층은 조회만 가능)
  • 단점
    • 같은 영속성 컨텍스트를 여러 트랜잭션이 공유
    • 프리젠테이션 계층에서 엔티티 수정 후 비즈니스 로직을 수정하면 엔티티가 수정될 수 있다
    • 프리젠테이션 계층에서 지연 로딩에 의한 SQL이 실행. 성능 튜닝 시 여러 계층을 확인해야한다.
  • OSIV사용하면 화면 출력 시 엔티티를 유지하면서 객체 그래프를 자유롭게 탐색할 수 있다.
    • 복잡한 화면을 구성할 때는 이 방법이 효과적이진 않다.
    • 여러 테이블을 조인하거나, 통계 화면을 보여줄 때 엔티티로 조회하기 보단 데이터들만 조회해서 DTO로 반환하는 것이 더 좋을 수도 있다.
  • 같은 JVM을 벗어난 원격 상황에서 사용할 수 없다.
    • 클라이언트가 필요한 데이터를 모두 JSON으로 생성해서 반환해야 한다.
    • JSON으로 생성한 API는 외부API와 내부API로 자누어진다
      • 외부API
        • 외부에 노출
        • 한 번 정의하면 변경이 어렵다
        • 엔티티는 생각보다 자주 변경되는데, 변경 시 노출하는 JSON API도 함께 변경되므로 엔티티를 직접 노출하기보단 DTO로 변환해서 노출하는 것이 안전(?)
      • 내부 API
        • 외부에 노출하지 않는다
        • 언제든지 변경 가능
        • 엔티티를 변경해도 클라이언트와 서버를 동시에 수정할 수 있기에 엔티티를 직접 노출해도 괜찮다.

 

 

 

 

 

Comments