[서론]
스프링에서는 트랜잭션이 시작되면 AOP 코드에 따라 내부적으로 EntityManager를 사용해 영속성컨텍스트를 만들어, 한 트랜잭션 내에서는 같은 영속성컨텍스트를 공유하게 끔 동작합니다.
진짜 그럴까 테스트해보면, 종종 @Transactional이 붙지 않았는데도 데이터들이 한 영속성 컨텍스트에서 공유되는 것을 볼 수 있습니다.
예를 들어 다음과 같이 id가 1인 객체를 두 번조서론]
스프링에서는 트랜잭션이 시작되면 AOP 코드에 따라 내부적으로 EntityManager를 사용해 영속성컨텍스트를 만들어, 한 트랜잭션 내에서는 같은 영속성컨텍스트를 공유하게 끔 동작합니다.
진짜 그럴까 테스트해보면, 종종 @Transactional이 붙지 않았는데도 데이터들이 한 영속성 컨텍스트에서 공유되는 것을 볼 수 있습니다.
예를 들어 서비스단에서 다음과 같이 id가 1인 객체를 두 번 조회하는 경우를 생각해봅시다.
(참고로 메서드와 클래스 모두에 @Transactoinal은 붙어있지 않습니다.)
room 객체를 조회했을 때, 트랜잭션 내가 아니라 영속성 컨텍스트에서 관리되지 않을 것이라고 예상할 수 있다.
그러므로 room1과 room2는 객체의 주소값이 달라 sout문에서 false를 출력할 것이다.
하지만 결과는 true가 나올 수 있다.
[본론]
결론적으로 경우에 따라 true가 나올 수 있는것은 스프링 osiv 설정이 true일 경우이다.(기본적으로 true로 설정되어 있다.)
이를 이해하기 위해서는 Spring 트랜잭션의 EntityManager 관리 방법을 조금 이해할 필요가 있다.
1. 트랜잭션의 EntityManager 관리방법
트랜잭션은 내부적으로 TreadLocal에 EntityManager를 저장해두고, 자신의 트랜잭션에 해당하는 EntityManager가 ThreadLocal에 있으면 해당 EntityManager를 조회해오고 아니면, 새로 EntityManager를 생성한다.
이 때 내부적으로 TransactionSynchronizationManager를 활용해 EntityManager를 ThreadLocal에서 가져오거나 저장한다.
이에 관해서는 아래 글에 자세히 적어두었으니 참고해주시면 좋을 거 같습니다.
https://yooooonshine.tistory.com/57
@Transactional을 붙이지 않았을 때에도 같은 EntityManager에서 관리되는 이유-osiv
OSIV란 트랜잭션이 종료된 후에도 영속성 컨텍스트를 유지하여 조회된 엔티티의 지연로딩을 가능하게 하는 방식이다.
(자세한 설명은 아래 링크를 참조하시면 좋을 거 같습니다.)
OSIV란? (feat.OSIV를 통한 성능 최적화)
OSIV(Open Session In View)란?OSIV란 Lazy Loading에 관련된 설정으로세션(세션이란 영속성 컨텍스트와 DB 커넥션을 관리하는 주체이다.)을 유저에게 응답할 때(View단, Controller단)까지 열어두거나, 닫아 두는
yooooonshine.tistory.com
영속성 컨텍스트는 일반적으로 트랜잭션의 범위 내에서 관리되는 것이 일반적이지만,
OSIV를 on으로 설정되면(default로 Spring은 OSIV를 on으로 설정하고 있다.) 영속성 컨텍스트가 Spring의 요청-응답 주기와 동일한 주기로 유지된다.
즉 영속성 컨텍스트가 요청부터 응답까지로 확장되기 때문에 트랜잭션이 없어도 같은 영속성 컨텍스트가 유지되는 것이다.
OSIV가 on일 때 영속성 컨텍스트가 준비되는 과정
OSIV가 on인 상태에서 요청이 들어오면, 서블릿 필터 혹은 스프링 인터셉터에서 EntityManager를 생성하여 ThreadLocal에 바인딩합니다.
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
EntityManagerFactory emf = this.lookupEntityManagerFactory(request);
boolean participate = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
String key = this.getAlreadyFilteredAttributeName();
if (TransactionSynchronizationManager.hasResource(emf)) {
participate = true;
} else {
boolean isFirstRequest = !this.isAsyncDispatch(request);
if (isFirstRequest || !this.applyEntityManagerBindingInterceptor(asyncManager, key)) {
this.logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter");
try {
EntityManager em = this.createEntityManager(emf);
EntityManagerHolder emHolder = new EntityManagerHolder(em);
TransactionSynchronizationManager.bindResource(emf, emHolder);
AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder);
asyncManager.registerCallableInterceptor(key, interceptor);
asyncManager.registerDeferredResultInterceptor(key, interceptor);
} catch (PersistenceException var17) {
PersistenceException ex = var17;
throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
}
}
}
boolean var16 = false;
try {
var16 = true;
filterChain.doFilter(request, response);
var16 = false;
} finally {
if (var16) {
if (!participate) {
EntityManagerHolder emHolder = (EntityManagerHolder)TransactionSynchronizationManager.unbindResource(emf);
if (!this.isAsyncStarted(request)) {
this.logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter");
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
}
}
}
if (!participate) {
EntityManagerHolder emHolder = (EntityManagerHolder)TransactionSynchronizationManager.unbindResource(emf);
if (!this.isAsyncStarted(request)) {
this.logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter");
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
}
}
서블릿 필터인 OpenEntityManagerInViewFilter를 살펴보면 doFilterInternal 메서드에서
HTTP 요청이 시작될 때 EntityManager를 생성 및 바인딩하고, 요청 처리가 끝나면 EntityManager를 정리하는 것을 볼 수 있습니다.
특히
if (TransactionSynchronizationManager.hasResource(emf)) {
participate = true;
} else {
이 부분에서 TransactionSynchronizationManager를 사용해 emf를 키로 하는 EntityManager가 있는 지 체크해 있으면 사용하고 없으면 새로 만드는 것을 볼 수 있습니다.
여기서 중요한 점은 Transaction와 Osiv 모두 TransactionSynchronizationManager를 통해 EntityManager를 조회한다는 것입니다.
이로 인해 Osiv에서 만들어진 영속성 컨텍스트를 트랜잭션 내에서도 사용하게 됩니
[결론]
OSIV가 on인 상태에서는 영속성 컨텍스트가 요청-응답까지 유지되므로 트랜잭션 없이도 두 번 조회된 데이터가 동일함을 보장받을 수 있다.
이는 OSIV로직과 트랜잭션 로직 모두 TranscationSynchronizationManager를 통해 EntityManager를 관리하기 때문이다.
'Spring' 카테고리의 다른 글
EntityManagerFactory, EntityManager, 영속성 컨텍스트와 영속성 컨텍스트의 특징 (0) | 2025.02.22 |
---|---|
영속성 컨텍스트의 1차 캐시는 어떻게 저장될까? (0) | 2025.02.22 |
@Profile과 @ActiveProfile (0) | 2025.02.20 |
Spring에서의 예외, 에러 처리 (0) | 2024.11.02 |
Spring에서의 프로세스와 스레드 (0) | 2024.10.21 |