JPA

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

iksadnorth 2023. 9. 7. 21:51

해당 게시물은 책 '자바 ORM 표준 프로그래밍'을 읽고 작성했습니다.

👣 개요

해당 게시글은 Spring Framework로 구성된 웹 App에서의 영속성 관리 방법에 대한 글이다.
Spring은 3 계층으로 웹 App을 관리하고 있고
각 계층에서의 올바른 JPA 영속성 관리 방법에 대해 이해하는 것은 매우 중요하다.
만약 영속성 컨텍스트의 생존 범위를 적절하지 못하게 설정한다면
원치하는 곳에서 Update 쿼리가 호출될 수도 있으며
지연 로딩에 의해 N+1 문제를 겪을 수도 있다.

 

👣 Spring Framework에서의 영속성 컨텍스트

스프링 컨테이너에서의 기본 영속성 컨텍스트의 생존 범위는
Service ~ Repository 이다.
동시에 트랜잭션의 범위 또한
Service ~ Repository 이다.

이는 Service 이외의 장소, 즉 Controller, View, Filter 등등의 장소에서
1. 엔티티 지연로딩을 허용하지 않을 뿐더러
2. 엔티티를 수정한다고 해서 Update 쿼리를 DB로 날리는 일을 막아준다.

2번의 경우, 클라이언트에게 보여줄 정보를 선별하는 과정 중,
실수로 엔티티 값을 수정해서 왜곡된 엔티티를 DB에 저장하지 않을 수 있는 이점이 있다.

하지만 1번의 경우, 필요에 따라 지연 로딩을 할 수 조차 없게 강제하기 때문에
유연성이 떨어지고 Controller에서의 로직이 Repository 까지 의존성을 드리우는 코드를 생산할 수 있다.
예를 들어, 쇼핑몰 웹사이트에서 상품 검색 페이지는 굳이 상품의 판매자를 같이 조회할 필요가 없기에
상품 정보만 조회했다가 급박한 기획팀의 요청에 의해 판매자 이름을 페이지에 조회해야 한다면
Controller, Service 계층의 로직을 수정해야 할 수도 있다.
왜냐 하면, Controller에는 Service Layer에서의 출력 결과만 받아올 수 있으므로
Service의 정보까지 가져와야 한다.

 

👣 Controller와 Service 사이의 의존성 지우기

만약 Controller가 급박하게 필요한 정보만 Service 수정없이 
조회하려면 다음과 같은 2가지 방식을 고려할 수 있다.

1. 뷰가 필요할 수도 있을 것 같은 엔티티를 미리 로딩하는 것.
2. OSIV를 사용해서 엔티티를 항상 영속성 컨텍스트에서 추적하는 것.

 

👣 미리 로딩 방법

필요할 수도 있는 데이터를 미리 로딩하는 방법은 
Service 코드를 굳이 고치지 않아도 Controller에서 마음대로 데이터를 선별해 클라이언트에게
전송할 수 있게 한다. 하지만 이런 방법은 필요하지도 않은 데이터까지 조회해 
불필요한 연산을 요구하는 방법이라 우선적인 최적화 대상일 것이다.

다음은 미리 엔티티를 로딩하는 방법 3가지다.

1. 글로벌 페치 전략 수정

위의 FetchType.LAZY를 FetchType.EAGER로 바꾼다.

해당 방법은 애초에 모든 연관관계를 미리 로딩해서 적재하는 방법이다.
하지만 이 방법은 N+1 문제를 야기하므로 되도록 자제하는 것이 좋다.

2. JPQL 페치 조인

Fetch Join을 이용하게 된다면 아예 1개의 쿼리로 모든 연관 관계의 엔티티를 불러오기 때문에 
N+1 문제도 해결된다. 하지만 이런 방법은 불러올 데이터가 매우 많다면 RAM에 큰 부하를 줄 수 있어서
대용량 데이터에 대해서는 부적절하다.

3. 강제 초기화

그냥 단순하게 필요하지 않지만 member.getTeam().getName()과 같은 오로지 
지연 로딩을 위한 코드를 추가하는 것이다.
하지만 이것은 말 그대로 초기화를 위한 코드이므로 원래의 비즈니스 코드와는 연관성이 없게 된다.
이것을 방지하고자 Controller와 Service 사이의 Facade 계층을 따로 놔서
코드 분리를 시켜준다면 유지보수성이 높아진다.
하지만 이 역시, 사용하지도 않을 데이터를 로딩해야 하는 것이기에
최적화의 대상이 되어야 할 것이다.

 

👣 OSIV

Open Session In View의 약자로서, 다음 그림과 같이 영속성 컨텍스트의 범위를 
Controller까지 감싸는 기술이다.

과거의 OSIV는 트랜잭션과 영속성 컨텍스트 생존 범위를 
요청과 응답 전체에 흩뿌려 놨었다.
하지만, 이것은 Controller에서의 엔티티 수정이 Update 쿼리를 날리는 참사로 이어질 수 있다.

때문에 이에 대한 답으로 영속성 컨텍스트는 요청-응답 사이를 모두 포괄하지만
트랜잭션의 범위는 Service를 넘어서지 못하게 설정하면 된다.

이렇게 함으로서 엔티티를 지연 로딩은 어디에서나 가능하지만
엔티티 수정에 의한 Dirty Check는 오로지 Service 이하의 Layer에서만 이뤄지게 된다.

해당 방식은 Spring Framework에서는 기본 설정이 되어 있지 않아 직접 설정해야 하지만
Spring Boot에서는 기본적으로 설정되어 있다.

하지만 때에 따라 이 조차도 허용하기 싫다면, 다음과 같은 설정을 통해 비활성화할 수 있다.

// application.properties
spring.jpa.open-in-view=false

 

'JPA' 카테고리의 다른 글

15장 고급 주제와 성능 최적화  (0) 2023.09.09
14장 컬렉션과 부가 기능  (0) 2023.09.08
12장 Spring Data JPA  (0) 2023.09.06
영속성 컨텍스트 vs JPQL  (0) 2023.09.06
QueryDSL  (0) 2023.09.06