준영속 상태의 지연로딩을 해결하는 방법
JPA에서 항상 생각을 해야되는 것이 바로 영속상태
, 영속성 컨텍스트위에 있는가?
를 생각해야된다.
예를 들어 우리는 DAO층에서 실제 DB와 통신을 보편적으로 진행하는데, 이쪽은 영속성 컨텍스트에 의해 관리가 되어
영속 상태를 유지한다.
하지만, Controller, View 이런 계층에서는 준영속 상태가 된다. 그래서 영속상태와 다르게 변경감지, 지연로딩이 동작하지 않게 된다.
지연로딩이 동작하지 않기 때문에 이때 지연 로딩을 시도하면 문제가 발생하는게 당연하다.
하이버네이트가 구현체라면 org.hibernate.LazyInitializationException
이 발생한다.
이것을 해결하는 문제는 두가지가 있다.
- 뷰가 필요한 엔티티를 미리 로딩
- OSIV를 사용해서 엔티티를 항상 영속 상태로 유지하는 방법
뷰가 필요한 엔티티를 미리 로딩
이름 그대로 영속성 컨텍스트가 살았을 때, 필요한 엔티티를 다 로딩하거나 초기화해서 반환하는 방법이다.
이미 다 로딩했기 떄문에, 지연 로딩이 발생할 걱정을 안해도 된다.
이 방법에서는 로딩해두는 방법에 따라 3가지로 나뉜다.
글로벌 페치 전략 수정
엔티티에서 fetch 타입을 변경하면 애플리케이션 전체에서 이 엔티티 객체를 로드할때 마다 해당 전략을 사용하므로 글로벌 페치 전략이라고 한다.
단점
사용하지 않는 엔티티까지 로딩
N + 1 문제 발생
JPQL을 사용할 때 문제가 발생하는데
Order와 Member가 다대일로 연결되어 있다고 가정한 후에select o from Order o
를 사용해서 조회를 한다고 한다면
글로벌 페치 전략을 사용하지 않고 그냥 JPQL자체만 사용하기 때문에
- 일단 Order를 조회
- order 인스턴스들 생성
- member 페치 전략이 즉시로딩이므로 order조회되면 member도 조회
- 근데 영속성 컨텍스트에 없다? order엔티티 수만큼 계속 조회
이걸 해결하려고 나온것이 바로 아래 부분이다.
JPQL 페치 조인
여기선 join fetch
를 사용하면 된다고 한다.select o from Order o join fetch o.member
N + 1 자체의 문제를 해결해주는것은 좋다. 근데 이제 order만 조회하느냐, order에 연관된 member까지 조회하냐에 따라 메소드를 늘려야할 것이다. 이러면 내부적으로 논리적 의존관계 우려가 발생하기 때문에 잘 고려해서 사용해야 할 것이다.
강제 초기화
지연 로딩을 설정했을 때 연관된 엔티티는 프록시 객체이다.
이 가짜 객체는 실제 사용 시점에 초기화가 되는데 이것을 영속성이 살아있을 때 다 초기화를 하여 반환해준다면? 준영속에서도 사용이 가능하다.
Facade 계층 추가
뷰를 위한 프록시 초기화 담당 계층
서비스와 컨트롤러를 분리해서 그 계층 사이의 의존성을 한번 더 분리해주는 것이라고 생각하면 편하다.
Controller - Facade - Service 이렇게 말이다.
프록시를 초기화하려면 영속성 컨텍스트가 필요해서 Facade에서 트랜잭션을 시작해야 한다.
퍼사드 계층 역할 및 특징
- 프리젠테이션, 도메인 모델 계층 간의 논리적 의존성 분리
- 프리젠테이션 계층에서 필요로 하는 프록시 객체 초기화
- 서비스 계층 호출하여 비즈니스 로직 실행
- Repository 직접 호출해서 엔티티 탐색
이것도 근데 결국 프리젠테이션 계층에서의 준영속 상태 라는 것이 문제이기 때문에 고안해낸 것.
OSIV
OSIV(Open Session In View)는 영속성 컨텍스트를 뷰까지 열어준다는 뜻.
뷰에서도 지연로딩이 가능❗️
프리젠테이션 계층에서 엔티티를 수정못하게 막는 방법은
- 엔티티를 읽기 전용 인터페이스로 제공
- 엔티티 래핑
- DTO만 반환
들이 있다.
스프링 OSIV
True - 기본값
application.properties에 추가해주면 된다.
sping.jpa.open-in-view:true
장점
- 그림과 같이 커넥션을 유지해서 영속성 컨텍스트 범위가 넓어진다.
- 그래서 연관관계 LAZY fetch 전략을 서비스를 벗어난 곳에서 사용 가능하다.
단점
- 너무 오랜시간동안 DB 커넥션을 유지하여 리소스를 사용하기 때문에 실시간 트래픽이 중요한 애플리케이션에서 DB 커넥션이 모자랄 수 있음 (장애 발생)
False - 설정값
sping.jpa.open-in-view : false
DB 커넥션을 Transaction
내부까지만 유지
(트랜잭션은 Service에서 수행되니까 Service까지만 유지되는 것)
- 장점
- DB 커넥션 리소스의 효율적인 사용
- 트랜잭션을 종료할 때 영속성 컨텍스트를 닫으면서 DB 커넥션을 반환
- 단점
- 모든 지연로딩을 트랜잭션 안에서 처리
- 지연로딩에 관한 모든 로직을
Service / Repository
에서 해결해야 함
해결방안
- Command와 Query를 분리하는 방법 (김영한님 선호)
- Controller에서 지연로딩을 처리해야 할 때 Query용 Service를 만드는 것
- OrderService
- 핵심 비즈니스 로직
- OrderQueryService
- 화면이나 API에 맞춘 서비스 (주로 읽기 전용 트랜잭션)
결론
결국 OSIV는 DB 커넥션 리소스에 대한 효율적인 사용과 관련된 전략이다.
OSIV 실무 TIP
실시간 API 고객 서비스를 해야한다면 OSIV false
설정
ADMIN 처럼 커넥션이 많지 않은 곳 OSIV true
설정
'JPA' 카테고리의 다른 글
고급 주제와 성능 최적화 1 (0) | 2022.08.06 |
---|---|
컬렉션과 부가기능 (0) | 2022.08.06 |
JPA metamodel must not be empty! (0) | 2022.08.06 |
[JPA] findAll, findById 차이 (0) | 2022.08.06 |