회사에 입사해서 코드를 살펴보던 중에
분명히 jpa entity 객체의 읽기 과정 중, 분명히 트랜잭션 처리가 필요한 코드가 보였다.
이를테면 Spring Security 인증을 거치고 Security Context Holder 에 저장된 UserPrinciple 객체
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : user")
annotation class CurrentUser(val require: Boolean = true)
이러한 애노테이션을 흔히들 전역적으로 Api 에서 파라미터로 주입 받아서 사용한다.
이를 테면,
@GetMapping
fun getProject(
@CurrentUser user : User
) : ProjectResponse {
//...
}
이러한 user 객체를 가지고 서비스 단에서 지연 로딩으로 getter를 통해 추가 프로퍼티를 조회할 때 사용했다.
나는 해당 객체가 준영속 상태라고 생각했었다. 당연히 user에서 조회할 때도 @Transactional 이 필요할 거라고 생각했다.
근데 이상하게도 트랜잭션 없이 프로퍼티들을 getter로 가져올 수 있었다.
시간을 들여 jpa 개념을 찾아보던 도중, 내가 잊고 있던 개념이 하나 생각나, 정리하게 되었다.
Spring Data JPA 의 영속성 컨텍스트 전략
영속성 컨텍스트 전략은 트랜잭션의 범위와 영속성 컨텍스트의 생존 범위가 같음을 이르는 전략이다.
트랜잭션이 시작 되면, 영속성 컨텍스트가 생성 되고,
트랜잭션이 종료 되면, 영속성 컨텍스트가 종료 된다.
그래서 보통 @Transactional 을 붙여 함수를 선언한다.
이 말은 트랜잭션 범위 밖의 컨트롤러나 뷰 단은 준영속 상태가 된다.
해서, 트랜잭션 범위 밖에서는 변경 감지가 일어나지 않는다.
트랜잭션 범위 밖에서 지연 로딩을 사용할 경우는, entity가 준영속 상태이므로,
LazyInitializationException 예외가 발생할 것이다.
이를 해결하기 위해,
- 필요한 엔티티를 미리 즉시로딩 하는 방법,
- OSIV 를 켜 놓고 엔티티를 영속 상태로 유지하는 방법 등이 있다.
첫 번째 방법은
Global Fetch 전략을 Eager로 설정하는 방법인데,
너무 단점이 크다.
바로 N+1 문제가 발생한다는 것으로 지양한다.
두 번째 방법은
영속 상태를 유지하기 위해,
DB 와의 커넥션을 계속 유지하게 되어 트래픽이 많이 몰리는 서비스의 경우 성능이 저하될 수 있다.
트랜잭션 없이 읽기
단순히 지연 로딩을 통해 단순 조회만 할 경우에는 @Transactional 을 선언할 필요가 없다.
위에 예제 코드에서 User Entity는 준영속 상태이지만,
영속성 컨텍스트 자체가 트랜잭션 범위 밖에서는 조회가 가능하므로,
getter를 통해 프로퍼티 조회가 가능하다.
fun getCard(user: User) : Card{
return user.card
}
이런 식으로 user의 프로퍼티인 card 를 단순히 읽는 것을 트랜잭션 없이 가능하다는 얘기이다.
user는 준영속 상태이지만, 영속성 컨텍스트는 트랜잭션 범위 밖에서도 읽기가 가능하기 때문이다.
앞으로 이러한 전략을 꾸준히 이용할 수 있을 것 같다.
'📗 JPA' 카테고리의 다른 글
MapStruct! JPA Entity 매핑 간 주의해서 사용하자 (0) | 2024.03.24 |
---|---|
JPA 집계함수 sum 은 long 을 반환한다. (0) | 2023.07.26 |
[Spring Data JPA] JPA Enum 필드에 관한 문제 (1) | 2023.02.21 |
[Spring Data JPA] 한방 쿼리의 효능 (feat : JPQL) (0) | 2023.02.01 |
JPA 개념 정리 (0) | 2022.07.15 |