본문 바로가기
  • 실행력이 모든걸 결정한다
Spring 사전 준비/JPA Hibernate

[JPA] 웹 애플리케이션에서의 영속성 관리

by 김코더 김주역 2022. 8. 22.
반응형

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

- 트랜잭션의 범위와 영속성 컨텍스트의 생존 범위가 같다.

- 트랜잭션이 종료되면 영속성 컨텍스트에 있던 엔티티들은 더 이상 영속 상태가 아니게 된다.

- 서로 다른 EntityManager를 사용하더라도 같은 트랜잭션 범위 안에 있다면 같은 영속성 컨텍스트를 사용한다.

- 같은 EntityManager를 사용하더라도 트랜잭션 범위가 다르다면 다른 영속성 컨텍스트를 사용한다. Spring 컨테이너는 스레드마다 각각 다른 트랜잭션을 할당하기 때문인데, 이 덕분에 멀티스레드 상황에도 안전하다.

 

 

 

2. 준영속 상태의 지연 로딩 문제

- 트랜잭션이 없는 프레젠테이션 계층에서 엔티티는 준영속 상태가 되기 때문에, 변경 감지와 지연 로딩이 동작하지 않는다.

※ 영속성 컨텍스트가 없을 때 지연 로딩을 시도하면 예외가 발생한다. Hibernate의 경우에는 LazyInitializationException 예외가 발생한다.

- 준영속 상태의 지연 로딩 문제를 해결하는 방법들을 알아보자.

 

 

1) 뷰가 필요한 엔티티를 미리 로딩하기

- 영속성 컨텍스트가 살아 있을 때 뷰에 필요한 엔티티들을 미리 다 로딩하거나 초기화해두는 방법이다.

 

(1) 글로벌 페치 전략(FetchType)을 즉시 로딩으로 설정

- 사용하지 않는 엔티티까지 로딩한다.

- 처음 조회한 데이터 수만큼 다시 SQL을 사용해야 하기 때문에 N+1 문제가 발생한다.

 

(2) JPQL 페치 조인 사용

- JPQL을 호출하는 시점에 함께 로딩할 엔티티를 선택한다.

- 페치 조인 사용 방법은 아래 포스팅의 [2-7) JOIN FETCH]를 참고하자.

https://kimcoder.tistory.com/493

 

[JPA] JPQL의 작성

1. JPQL 소개 - JPQL(Java Persistence Query Language)은 객체지향 SQL이다. - SQL이 DB 테이블을 대상으로 하는 데이터 중심의 쿼리라면, JPQL은 엔티티 객체를 대상으로 하는 객체지향 쿼리다. JPA는 JPQL을 분..

kimcoder.tistory.com

- 글로벌 페치 전략 설정보다 우선순위가 높다.

- N+1 문제가 해결된다.

- 프레젠테이션 계층에 의존하는 Repository 메소드가 증가한다는 단점이 있다.

 

(3) 강제로 초기화

- 영속성 컨텍스트가 살아있을 때 프레젠테이션 계층이 필요한 엔티티를 강제로 초기화하는 방법이다.

- 프록시 객체는 사용되는 시점에 초기화 된다는 특징을 이용해서 프록시 객체의 메소드를 그냥 호출하는 것이다.

- Hibernate는 프록시 객체를 강제로 초기화하는 initialize() 메소드를 제공한다.

org.hibernate.Hibernate.initialize(member.getTeam());

- 참고로, JPA가 제공하는 PersistenceUnitUtil의 isLoaded() 메소드를 사용하면 특정 프록시 객체의 초기화 여부를 확인할 수 있다.

boolean isLoad = em.getEntityManagerFactory().getPersistenceUnitUtil.isLoaded(member.getTeam());

- 조회한 엔티티가 프록시 객체인지 실제 엔티티 객체인지 확인하기 위해 클래스명을 확인해볼 수도 있다. 프록시의 경우에는 클래스명의 뒤쪽에 javassist가 표시된다.

- 서비스 계층이 프레젠테이션 계층에 의존하게 된다는 단점이 있다.

 

(4) FACADE 계층 추가

- 프레젠테이션 계층과 서비스 계층 사이에 FACADE 계층을 추가해서, 서비스 계층은 비즈니스 로직에 집중하고 프레젠테이션 계층을 위한 프록시 초기화 코드는 모두 FACADE 계층이 담당하게 된다.

- FACADE 계층에서 트랜잭션이 시작되어 서비스 계층이나 리포지토리를 직접 호출한다.

- 계층이 하나 더 추가되는 만큼 더  많은 코드를 작성해야 된다는 단점이 있다.

 

뷰가 필요한 엔티티를 미리 로딩하는 방법에는 여러 가지가 있지만 각각 명확한 단점이 존재한다.

 

 

2) OSIV

- 영속성 컨텍스트를 뷰까지 열어둬서 뷰에서도 지연 로딩을 사용할 수 있게 하는 방법이다.

- FACADE 계층 없이도 독립적인 서비스 계층을 유지할 수 있다.

- 잠시 후에 설명할 [3. OSIV]에서 자세하게 살펴보자.

 

 

 

3. OSIV

- Open Session In View의 약어로, 영속성 컨텍스트를 뷰까지 열어두는 기술이다.

 

 

1) 과거의 OSIV

- 과거의 OSIV는 요청 단위의 트랜잭션 방식을 이용했다. 이 방식은 프레젠테이션 계층에서 엔티티를 변경할 수 있기 때문에 개선이 필요했다.

- 프레젠테이션 계층에서 엔티티를 수정하지 못하게 막는 방법들은 다음과 같다.

 

(1) 읽기 전용 인터페이스 제공

- 프레젠테이션 계층에 엔티티를 노출하는 대신에 읽기 전용 메소드만 모은 인터페이스를 제공하는 방법이다.

@Entity
public class Member implements MemberView {...}

- 반환된 엔티티에서 사용할 수 있는 메소드는 읽기 전용 메소드 뿐이다.

public class MemberService {
    public MemberView getMember(long id) { // 읽기 전용 인터페이스를 사용
        return memberRepository.findById(id);
    }
}

 

(2) 엔티티 래핑

- 프레젠테이션 계층에 엔티티를 노출하는 대신에 엔티티를 감싸서 읽기 전용 메소드만 작성하는 래퍼 클래스를 제공하는 방법이다.

public class MemberWrapper {
    
    private Member member;
    
    public MemberWrapper(Member member) {
        this.member=member;
    }
    
    /* 읽기 전용 메소드 작성 */
}

 

(3) DTO만 반환

- 단순히 데이터만 전달하는 객체인 DTO를 반환하는 방법이다.

- 엔티티를 거의 복사한 듯한 DTO 클래스를 새로 만들어야 한다는 단점이 있다.

 

이러한 방법들은 코드량이 상당히 증가한다는 단점이 존재한다.

 

 

2) Spring OSIV

- 비즈니스 계층에서만 트랜잭션을 유지하는 OSIV 방식이다.

 

(1) OSIV 라이브러리 소개 및 적용

<1> 라이브러리 추가

- org.springframework.orm 라이브러리를 사용하면 OSIV 라이브러리도 이용할 수 있다.

https://mvnrepository.com/artifact/org.springframework/org.springframework.orm/3.2.2.RELEASE

 

<2> 서블릿 필터 클래스와 스프링 인터셉터 클래스

- 원하는 클래스를 서블릿 필터나 스프링 인터셉터에 등록하면 된다.

  • Hibernate OSIV 서블릿 필터 : org.springframework.orm.hibernate4.support.OpenSessionInViewFilter
  • Hibernate OSIV 스프링 인터셉터 : org.springframework.orm.hibernate4.support.OpenSessionInViewInterceptor
  • JPA OEIV 서블릿 필터 : org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
  • JPA OEIV 스프링 인터셉터 : org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor

※ 참고) OpenEntityManagerInViewFilter 적용 : http://chomman.github.io/blog/java/programming/spring%20framework/spring-jpa-%EC%82%AC%EC%9A%A9%EC%8B%9C-Lazyinitializationexception-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95/

 

<3> application.properties로 적용

- spring.jpa.open-in-view=true로 적용하면 OSIV가 활성화된다.

- Spring Boot에서는 OSIV가 기본적으로 활성화되어 있다. 즉, spring.jpa.open-in-view 속성이 true로 되어 있다. 만약 OSIV를 끄고 싶다면 false를 주면 된다.

spring.jpa.open-in-view=false

 

 

(2) 동작 원리

- 영속성 컨텍스트의 생존 범위는 요청 동안이고, 트랜잭션의 범위는 서비스 계층이다. 서비스 계층이 끝나고 트랜잭션을 커밋하면서 영속성 컨텍스트를 flush하더라도 영속성 컨텍스트는 종료되지 않는다.

- 요청이 끝나면 flush를 호출하지 않고 영속성 컨텍스트만 종료한다. 그래서 비즈니스 계층 외에서는 엔티티를 수정해도 수정 내용이 데이터베이스에 반영되지 않는다.

 

(3) 트랜잭션 없이 읽기

- 영속성 컨텍스트는 트랜잭션 없이(범위 밖에서) 엔티티를 조회하는 것이 가능하다. 즉, 프레젠테이션 계층에서 지연 로딩 기능을 사용할 수 있게 된다.

※ 물론, 트랜잭션 없이 수정은 불가능하다.

 

(4) 주의 사항

- 트랜잭션 범위 밖에서 엔티티를 수정했더라도 영속성 컨텍스트는 요청 동안에는 살아있기 때문에, 직후에 트랜잭션을 시작하는 서비스 계층을 호출하면 원치 않은 수정 내용이 반영될 수도 있다. Spring OSIV는 같은 영속성 컨텍스트를 여러 트랜잭션이 공유할 수 있고, 하나의 서비스 계층이 종료되면서 트랜잭션이 커밋되면 변경 감지 내용이 반영된 영속성 컨텍스트가 flush되기 때문에 이런 상황이 발생하는 것이다. 이러한 문제는 트랜잭션이 있는 비즈니스 로직을 모두 호출하고 나서 엔티티를 변경하는 방식으로 해결할 수 있다.

- OSIV를 사용하는 방법이 만능은 아니다. 수 많은 테이블을 조인해야 하는 상황에서는 DTO를 만들어서 사용하는 것이 낫다.

- 원격지인 클라이언트에서 연관된 엔티티를 지연 로딩하는 것은 불가능하다.

 

 

● 참고자료 : 자바 ORM 표준 JPA 프로그래밍

 

반응형

'Spring 사전 준비 > JPA Hibernate' 카테고리의 다른 글

[JPA] Entity와 Table간의 데이터 타입 변환  (0) 2022.08.24
[JPA] JPA와 컬렉션  (0) 2022.08.24
[JPA] Spring Data JPA(2)  (0) 2022.08.20
[JPA] Spring Data JPA(1)  (0) 2022.08.19
[JPA] JPQL 최적화  (0) 2022.08.14

댓글