반응형
Notice
Recent Posts
Recent Comments
관리 메뉴

간단한 개발관련 내용

13장 웹 애플리케이션 영속성 관리 본문

IT 책/JPA (ORM 표준 JPA 프로그래밍)

13장 웹 애플리케이션 영속성 관리

vincenzo.dev.82 2024. 11. 19. 13:57
반응형

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

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

<aside> 💡

스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다.

이 전략은 이름 그대로 트랜잭션의 범위와 영속성 컨텍스트의 생존 범위가 같다는 뜻이다. 트랜잭션을 시작할 때 영속성 컨텍스트를 생성하고 트랜잭션이 끝날 때 영속성 컨텍스트를 종료한다. 그리고 같은 트랜잭션 안에서는 항상 같은 영속성 컨텍스트에 접근한다.

</aside>

  • @Transactional 어노테이션이 있으면 호출한 메서드를 실행하기 직전에 스프링의 AOP가 먼저 동작한다.
  • 트랜잭션이 같으면 같은 영속성 컨텍스트를 사용한다
  • 트랜잭션이 다르면 다른 영속성 컨텍스트를 사용한다
    • 같은 엔터티매니저를 사용해도 쓰레드 마다 생성된 트랜잭션에 따라 다르다

13.2 준영속 상태와 지연 로딩 (582page)

  • 준영속 상태와 변경감지
  • 준영속 상태와 지연로딩
    • 준영속 상태는 영속성 컨텍스트가 없으므로 지연 로딩을 할 수 없다. 이때 지연로딩을 시도하면 LazyInitializationException이 발생한다
  • 준영속 상태에서 지연로딩 문제를 해결하는 방법은 크게 2가지가 있다.
    • 뷰가 필요한 엔터티를 미리 로딩해 둔다
    • OSIV를 사용해서 엔터티를 항상 영속상태로 유지하게 한다
  • 엔터티를 미리 로딩하는 방법은 3가지가 있다.
    • 글로벌 페치 전략 수정
    • JPQL fetch join
    • 강제로 초기화

13.2.1 글로벌 패치 전략 수정

  • 즉시로딩으로 수정한다.
  • 글로벌 패치 전략에 즉시로딩 사용 시 단점
    • 사용하지 않는 엔터티를 로딩한다
    • N+1 문제가 발생한다

13.2.2 JPQL 페치 조인

  • 글로벌 페치 전략을 즉시 로딩으로 설정하면 애플리케이션 전체에 영향을 주므로 너무 비효율적이다.
  • 페치 조인은 조인 명령어 마지막에 fetch를 넣어주면 된다.

13.2.3 강제로 초기화

  • 글로벌 페치 전략을 지연로딩으로 설정하면 연관된 엔터티를 실제 엔터티가 아닌 프록시 객체로 조회한다. 프록시 객체는 실제 사용하는 시점에초기화된다.
    • order.getMember().getName()
    • 멤버만이 아나리 멤버의 함수를 호출해서 프락시 초기화를 한다

13.2.4 FACADE 계층 추가

  • 프리젠테이션과(컨드롤러) 서비스(서비스) 사이에 FACADE를 둔다
  • FACADE 계층의 역할과 특징
    • 프리젠테이션 계층과 도메인 모델 계층 간의 논리적 의존성을 분리해준다.
    • 프리젠테이션 계층에서 필요한 프록시 객체를 초기화한다
    • 서비스 계층을 호출해서 비즈니스 로직을 실행한다
    • 리포지터리를 직접 호출해서 뷰가 요구하는 엔터티를 찾는다

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

 

13.3 OSIV

  • Open Session In View는 영속성 컨텍스트를 뷰까지 열어둔다는 것이다. 따라서 뷰에서도 지연로딩을 사용할 수 있다는 뜻이다.

13.3.1 과거 OSIV : 요청 당 트랜잭션

  • 요청 당 트랜잭션 방식의 OSIV 문제점
    • 요청 당 트랜잭션 방식의 OSIV가 가지는 문제점은 컨트롤러나 뷰 같은 프리젠테이션 계층이 엔터티를 변경할 수 있다는 것이다
    • 가령 화면에 고객이름 중간에 별표 표시를 하고만 싶었는데, 데이터베이스에도 반영되어 버린다
  • 프리젠테이션에서 엔터티를 수정하지 못하게 막는 방법
    • 엔터티를 읽기 전용 인터페이스로 제공
    • 엔터티 래핑
    • DTO만 반환
      • OSIV의 장점도 없고 DTO도 추가되는 단점

13.3.2 스프링 OSIV : 비즈니스 계층 트랜잭션

  • 스프링 프레임워크가 제공하는 OSIV 라이브러리
    • 하이버네이트 OSIV 서블릿 필터
    • 하이버네이트 OSIV 스프링 인터셉터
    • JPA OEIV 서블릿 필터
    • JPA OEIV 스프링 인터셉터
  • 트랜잭션 없이 읽기
    • 영속성 컨텍스트가 트랜잭션 밖에서 엔터티를 조회할 수 있는 것을 말함

13.3.3 OSIV 정리

13.4 너무 엄격한 계층

13.5 정리

 


OSIV (Open Session In View)란?

  • *OSIV (Open Session In View)**는 Spring JPA/Hibernate에서 기본적으로 활성화된 설정입니다. 이 패턴은 영속성 컨텍스트(Persistence Context) 또는 **Hibernate 세션(Session)**을 웹 요청의 시작부터 끝까지 유지하여, 트랜잭션 범위를 벗어난 곳에서도 엔티티와 연관된 지연 로딩(Lazy Loading)된 데이터를 사용할 수 있도록 해줍니다.

즉, 클라이언트의 요청이 들어오면 데이터베이스 연결과 영속성 컨텍스트가 열리며, 뷰 렌더링 시점까지 이를 유지하여 컨트롤러나 뷰에서 지연 로딩된 엔티티에 접근할 수 있게 해줍니다. 이는 개발 편의성을 높여주지만, 여러 가지 문제점도 발생할 수 있습니다.

OSIV의 주요 동작

  • 트랜잭션 범위 바깥에서 지연 로딩 가능: 트랜잭션이 끝난 이후에도 Hibernate 세션이 열려 있기 때문에, 뷰나 컨트롤러에서 Lazy 로딩된 연관 엔티티에 접근할 수 있습니다. 이로 인해 LazyInitializationException을 방지할 수 있습니다.
    • 예시: 컨트롤러에서 엔티티의 연관 필드를 사용하려 할 때, 트랜잭션이 끝났음에도 지연 로딩된 데이터를 조회할 수 있습니다.

OSIV의 문제점

OSIV는 개발 편의성을 제공하지만, 특히 성능과 관련된 문제를 유발할 수 있습니다.

  1. 데이터베이스 커넥션의 장시간 유지
    • OSIV를 활성화하면 웹 요청의 시작부터 끝까지 영속성 컨텍스트와 데이터베이스 연결을 유지하게 됩니다. 이는 웹 요청이 길어질수록, 즉 뷰 렌더링이 느리거나 많은 데이터가 로딩될수록 데이터베이스 커넥션이 장시간 점유될 수 있습니다.
    • 그 결과, 커넥션 풀이 고갈될 위험이 높아져 시스템의 확장성이나 성능에 문제가 발생할 수 있습니다.
  2. 잘못된 지연 로딩 남발로 인한 성능 저하
    • OSIV가 활성화된 상태에서는 컨트롤러나 뷰에서 지연 로딩된 데이터를 쉽게 조회할 수 있습니다. 하지만 이로 인해 N+1 문제와 같은 성능 이슈가 발생할 수 있습니다.
    • 예를 들어, 컨트롤러나 뷰에서 여러 연관 엔티티를 반복적으로 조회하면 데이터베이스에 다수의 쿼리가 실행되며, 이는 심각한 성능 저하를 초래할 수 있습니다.
  3. 비즈니스 로직과 프레젠테이션 계층의 결합
    • OSIV가 활성화된 상태에서는 컨트롤러나 뷰에서 비즈니스 로직을 실행할 수 있는 유혹이 생깁니다. 예를 들어, 뷰에서 지연 로딩된 엔티티를 사용해 추가 데이터를 조회하거나, 의도하지 않은 로직이 실행될 수 있습니다.
    • 이는 비즈니스 로직과 프레젠테이션 계층 간의 경계를 흐리게 하며, 애플리케이션 구조가 복잡해지고 유지보수성이 떨어질 수 있습니다.
  4. 지연 로딩 시점 불확실성
    • OSIV가 활성화되어 있으면 지연 로딩이 트랜잭션 종료 후에 발생할 수 있는데, 이는 로직의 예측 가능성을 저하시킵니다. 예를 들어, 어떤 코드에서 데이터베이스에 접근하는 시점을 개발자가 명확히 통제하기 어려워질 수 있습니다.
    • 이는 성능 문제뿐만 아니라 디버깅이 어려워지는 문제로 이어질 수 있습니다.
  5. 보안 문제
    • 컨트롤러나 뷰에서 지연 로딩된 엔티티에 접근할 수 있으므로, 보안상의 이유로 노출되어서는 안 되는 데이터가 의도치 않게 렌더링될 위험이 있습니다. 이는 데이터 접근을 뷰 렌더링 과정에서 동적으로 처리할 수 있다는 점에서 보안에 취약할 수 있습니다.

OSIV 비활성화의 장점

  1. 트랜잭션 내에서 모든 데이터 로딩: OSIV를 비활성화하면 모든 데이터베이스 접근이 트랜잭션 안에서 일어나게 됩니다. 즉, 서비스 계층에서 엔티티의 모든 연관된 데이터를 명시적으로 로딩한 후 뷰로 전달하게 됩니다. 이는 데이터 로딩 시점을 명확하게 하고, 성능 문제를 예방할 수 있습니다.
  2. N+1 문제 예방: 모든 데이터 로딩이 서비스 계층에서 이루어지므로, 필요한 데이터를 미리 로드하는 방식으로 N+1 문제를 예방할 수 있습니다. JOIN FETCH나 @EntityGraph를 사용하여 연관 데이터를 한 번에 로드할 수 있습니다.
  3. 영속성 컨텍스트 종료로 인한 자원 효율성: 트랜잭션이 종료되면 영속성 컨텍스트와 데이터베이스 커넥션도 종료되므로, 자원을 더 효율적으로 사용할 수 있습니다. 이는 데이터베이스 커넥션을 적절히 관리하고, 커넥션 풀이 고갈되는 문제를 줄여줍니다.
  4. 레이어드 아키텍처의 분리 강화: OSIV 비활성화 시 프레젠테이션 계층에서 지연 로딩된 데이터를 처리하지 않게 되므로, 비즈니스 로직과 프레젠테이션 계층의 명확한 분리가 이루어집니다. 이는 코드의 유지보수성과 구조적 안정성을 높여줍니다.

OSIV 비활성화 방법

Spring Boot에서는 기본적으로 OSIV가 활성화되어 있지만, 이를 비활성화할 수 있습니다. application.properties 파일에서 OSIV를 비활성화하려면 다음 설정을 추가하면 됩니다:

spring.jpa.open-in-view=false

이 설정을 통해 OSIV를 비활성화하면, 모든 데이터베이스 접근은 트랜잭션 안에서만 이루어지게 됩니다. Lazy 로딩된 엔티티에 접근하려면 서비스 계층에서 미리 연관 데이터를 로딩하거나, JOIN FETCH 또는 @EntityGraph를 사용해 데이터를 명시적으로 가져와야 합니다.

결론

OSIV는 개발 편의성을 제공하지만, 성능 저하와 커넥션 관리 문제를 일으킬 수 있습니다. 특히 대규모 애플리케이션에서는 성능 이슈가 발생할 가능성이 크기 때문에, OSIV를 비활성화하고 트랜잭션 내에서 데이터 로딩을 명시적으로 관리하는 것이 권장됩니다. N+1 문제를 예방하고 영속성 컨텍스트와 데이터베이스 커넥션을 적절히 관리하려면, OSIV를 비활성화하고 서비스 계층에서 필요한 데이터를 미리 로딩하는 방식을 사용하는 것이 더 안전하고 성능에 유리합니다.

 

반응형