웹 애플리케이션과 영속성 관리
트랜잭션 범위의 영속성 컨텍스트
웹 애플리케이션에서 트랜잭션 범위의 영속성 컨텍스트는 데이터베이스와의 상호작용을 효율적으로 관리하는 데 필수적이다. 트랜잭션 범위의 영속성 컨텍스트는 일반적으로 요청 당 트랜잭션으로 설정되며, 이 경우 모든 엔티티 객체는 해당 트랜잭션 내에서 관리된다. 이는 데이터의 일관성을 유지하고, 중복된 데이터베이스 접근을 줄이는 데 도움을준다.
스프링 컨테이너의 기본 전략
스프링 컨테이너는 데이터베이스 작업을 관리하기 위해 @Transactional 어노테이션을 사용하여 트랜잭션을 처리한다. 기본적으로 스프링은 각 요청마다 새로운 트랜잭션을 시작하고, 작업이 완료되면 자동으로 커밋하거나 예외가 던져지면 롤백한다. 이러한 접근 방식은 데이터 일관성을 보장하고, 예외 발생 시 롤백을 통해 데이터베이스의 무결성을 유지한다.
[그림 2]와 같이 트랜잭션이 같으면 같은 영속성 컨텍스트를 사용한다. 따라서 엔티티 매니저는 달라도 같은 영속성 컨텍스트를 사용한다.
반면에 [그림 3]을 보면 트랜잭션이 다르면 다른 영속성 컨텍스트를 사용하는 것을 볼 수 있다. 따라서 같은 엔티티 매니저를 호출해도 접근하는 영속성 컨텍스트가 다르므로 멀티스레드 상황에 안전하다.
준영속 상태와 지연 로딩
준영속 상태와 지연 로딩은 JPA의 중요한 개념이다. 준영속 상태는 영속성 컨텍스트에서 분리된 엔티티의 상태를 의미하며, 지연 로딩은 엔티티가 실제로 필요할 때까지 데이터를 로딩하지 않는 전략이다.
만약 트랜잭션이 종료되어 준영속 상태인 엔티티에 대하여 지연 로딩이 실행된다면 어떨까?
글로벌 페치 전략 수정
JPA에서 글로벌 페치 전략을 수정하면, 엔티티의 연관 관계를 어떻게 로딩할지 제어할 수 있다. 글로벌 페치 전략은 fetch = FetchType.EAGER 또는 fetch = FetchType.LAZY로 설정할 수 있으며, 기본적으로는 지연 로딩(LAZY)을 사용하는 것을 권장한다. 그러나 즉시 로딩(EAGER)을 사용하면, 연관된 엔티티를 즉시 로딩하기 때문에 다음과 같은 단점이 발생할 수 있다.
- N+1 문제: 즉시 로딩은 연관된 엔티티를 불필요하게 여러 번 로딩할 수 있다. 이로 인해 N+1 문제(부모 엔티티를 로딩할 때 자식 엔티티를 추가적으로 N번 로딩하는 문제)가 발생할 수 있다.
- 성능 저하: 많은 데이터를 한 번에 로딩하기 때문에 성능이 저하될 수 있다. 이는 특히 큰 데이터셋을 가진 애플리케이션에서 문제가 될 수 있다.
- 불필요한 데이터 로딩: 실제로 필요하지 않은 데이터까지 로딩하게 되어 메모리 사용량이 증가할 수 있다.
JPQL 페치 조인
JPQL 페치 조인은 JOIN FETCH 절을 사용하여 연관된 엔티티를 한 쿼리로 로딩할 수 있게 해다. 이는 성능을 개선할 수 있지만 다음과 같은 단점이 있다.
- 쿼리 복잡성: JPQL 페치 조인을 사용하면 쿼리가 복잡해지고, 유지보수가 어려울 수 있다. 복잡한 조인 쿼리는 읽기 어렵고, 디버깅이 힘들 수 있다.
- 데이터 중복: 페치 조인을 사용할 때, 연관된 엔티티를 함께 로딩하므로 데이터가 중복될 수 있다. 특히, JOIN FETCH를 사용할 경우 중복된 데이터가 포함되어 성능 저하를 초래할 수 있다.
- 과도한 데이터 로딩: 연관된 모든 데이터를 로딩하게 되므로, 불필요한 데이터까지 함께 로딩될 수 있다. 이는 메모리 사용량을 증가시킬 수 있다.
강제로 초기화
강제로 초기화는 지연 로딩된 엔티티를 수동으로 초기화하여 필요한 데이터를 미리 로딩하는 방법이다. 이 방법은 N+1 문제를 해결하거나, 특정 데이터가 반드시 필요할 때 유용하지만, 실제 사용 시 주의가 필요하다.
강제로 초기화할 때 controller가 비즈니스 로직에 간접적으로 관여하게될 수도 있다. 이 때문에 FACADE 계층을 추가 하는 것을 권장한다.
FACADE 계층 추가
FACADE 계층은 비즈니스 로직을 제공하는 데 도움을 주며, 데이터 접근 로직과 사용자 인터페이스 사이의 중개 역할을 한다. 이 계층을 추가함으로써 다음과 같은 장점이 있다.
- 응집력 향상: 비즈니스 로직과 데이터 접근 로직을 분리하여, 응집력 높은 코드 구조를 유지할 수 있다.
- 모듈화: 비즈니스 로직을 모듈화하여, 코드의 재사용성과 유지보수성을 높일 수 있다.
하지만 다음과 같은 단점이 있을 수 있다.
- 추가적인 복잡성: FACADE 계층을 추가하면 코드베이스가 복잡해지고, 이해하기 어려울 수 있다. 이는 코드의 유지보수 비용을 증가시킬 수 있다.
- 성능 이슈: FACADE 계층이 중간에서 모든 요청을 처리하게 되면, 추가적인 오버헤드가 발생할 수 있다.
준영속 상태와 지연 로딩의 문제점
준영속 상태와 지연 로딩을 사용하는 데는 몇 가지 문제점이 있을 수 있다.
- 데이터 정합성 문제: 준영속 상태의 엔티티는 영속성 컨텍스트와 분리되어 있기 때문에, 데이터의 정합성이 깨질 수 있다. 이로 인해 불일치된 데이터가 애플리케이션에서 처리될 수 있다.
- 지연 로딩 이슈: 지연 로딩이 설정된 엔티티는 실제로 필요할 때까지 로딩되지 않으므로, 이로 인해 LazyInitializationException 같은 예외가 발생할 수 있다. 이 문제를 해결하기 위해 필요한 데이터를 미리 로딩하거나, 영속성 컨텍스트의 범위를 조정하는 방법이 필요하다.