1. 트랜잭션의 EntityManager 관리방법
서비스 단에 @Transactional이 붙은 메서드 호출이 되면 Spring AOP 프록시가 요청을 가로채고, TransactionIntercepter를 호출한다.
이 TransactionIntercepter에는 invoke 메서드가 위와 같이 구현되어 있다.
내부적으로 invokeWithinTrasaction 메서드를 실행시키고 이 내부를 들여다 보면
위와 같은 코드가 들어있다.
위 코드는 내부적으로 TransactionManager를 사용하며, jpa의 경우 JpaTransactionManager를 사용한다.
그리고 JpaTransactionManager는 내부적으로 트랜잭션을 시작하기 위해 doGetTransaction() 메서드를 호출한다.
doGetTransaction()은 최종적으로 TransactionSynchronzationManager.getResource()를 통해 EntityManager를 가져와 사용한다.
코드 내부적으로 트랜잭션이 이미 존재하는 경우에는 ThreadLocal에서 동일한 EntityManager를 가져오고, 존재하지 않으면 새로운 트랜잭션을 설정할 준비를 한다.
이 때 TransactionSynchronzationManager은 ThreadLocal(일명 쓰레드)에 직접적으로 데이터를 넣고 꺼내는 보관소 역할을 한다. 즉 Thread-Safe하며, 다른 쓰레드에서는 데이터를 사용할 수 없다.
만약 EntityManger가 없으면 아래와 같은 doBegin 메서드가 호출되어 EntityManager를 생성해 저장한다.
protected void doBegin(Object transaction, TransactionDefinition definition) {
JpaTransactionObject txObject = (JpaTransactionObject)transaction;
if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
throw new IllegalTransactionStateException("Pre-bound JDBC Connection found! JpaTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single JpaTransactionManager for all transactions on a single DataSource, no matter whether JPA or JDBC access.");
} else {
try {
EntityManager em;
if (!txObject.hasEntityManagerHolder() || txObject.getEntityManagerHolder().isSynchronizedWithTransaction()) {
em = this.createEntityManagerForTransaction();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Opened new EntityManager [" + em + "] for JPA transaction");
}
txObject.setEntityManagerHolder(new EntityManagerHolder(em), true);
}
em = txObject.getEntityManagerHolder().getEntityManager();
int timeoutToUse = this.determineTimeout(definition);
Object transactionData = this.getJpaDialect().beginTransaction(em, new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder()));
txObject.setTransactionData(transactionData);
txObject.setReadOnly(definition.isReadOnly());
if (timeoutToUse != -1) {
txObject.getEntityManagerHolder().setTimeoutInSeconds(timeoutToUse);
}
if (this.getDataSource() != null) {
ConnectionHandle conHandle = this.getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
if (conHandle != null) {
ConnectionHolder conHolder = new ConnectionHolder(conHandle);
if (timeoutToUse != -1) {
conHolder.setTimeoutInSeconds(timeoutToUse);
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Exposing JPA transaction as JDBC [" + conHandle + "]");
}
TransactionSynchronizationManager.bindResource(this.getDataSource(), conHolder);
txObject.setConnectionHolder(conHolder);
} else if (this.logger.isDebugEnabled()) {
this.logger.debug("Not exposing JPA transaction [" + em + "] as JDBC transaction because JpaDialect [" + this.getJpaDialect() + "] does not support JDBC Connection retrieval");
}
}
if (txObject.isNewEntityManagerHolder()) {
TransactionSynchronizationManager.bindResource(this.obtainEntityManagerFactory(), txObject.getEntityManagerHolder());
}
txObject.getEntityManagerHolder().setSynchronizedWithTransaction(true);
} catch (TransactionException var9) {
TransactionException ex = var9;
this.closeEntityManagerAfterFailedBegin(txObject);
throw ex;
} catch (Throwable var10) {
Throwable ex = var10;
this.closeEntityManagerAfterFailedBegin(txObject);
throw new CannotCreateTransactionException("Could not open JPA EntityManager for transaction", ex);
}
}
}
요약
- Spring은 트랜잭션이 시작되면 스프링 AOP를 통해 TransactionIntercepter를 호출합니다.
- 호출된 TransactionIntercepter는 TransactionManager의 구현체인 JpaTransactionManager를 통해 트랜잭션을 시작합니다.
- JpaTransactionManager는 ThreadLocal에 존재하는 트랜잭션에 맞는 EntityManager가 있는 지 체크하여 있으면 가져옵니다.
- 없으면 새로 생성합니다
2. Bean으로 주입받는 EntityManger의 관리방식
@Repository
public class UserRepository {
@PersistenceContext // 혹은 @Bean 사용
private EntityManager entityManager;
public User findById(Long id) {
return entityManager.find(User.class, id);
}
}
@Bean 애노테이션을 통해 EntityManager를 주입받아도 되며 Jpa의 @PersisntenceContext 애노테이션으로 EntityManager를 주입받아도 된다.
(@PersistenceContext와 동작원리에 대해서는 아래 글을 참조하면 좋을 거 같습니다.)
https://yooooonshine.tistory.com/53
이렇게 주입 받은 EntityManager는 찐 EntityManager가 아니라 Proxy객체이다.
그리고 이 Proxy 객체에 진짜 EntityManager를 주입하는 코드는
ShareEntityManagerCreator.invoke 메서드를 통해 볼 수 있다.
위 메서드는 내부적을 EntityManagerFactoryUtils.doGetTransactionEntityManager를 호출하며
doGetTransactionalEntityManager는 내부적으로 TransactionSynchronizationManager를 통해 EntityManager를 찾는다.
이 때부터는 위의 Transaction에서와 동일하게 ThreadLocal을 활용해 조회해온다.
'Spring' 카테고리의 다른 글
LazyInitializationException 해결기 (0) | 2025.02.22 |
---|---|
EntityManagerFactory, EntityManager, 영속성 컨텍스트와 영속성 컨텍스트의 특징 (0) | 2025.02.22 |
영속성 컨텍스트의 1차 캐시는 어떻게 저장될까? (0) | 2025.02.22 |
@Transactional 내에서가 아니여도 OSIV가 on이면 조회한 데이터들이 같은 영속성 컨텍스트를 공유하는 이유 (0) | 2025.02.22 |
@Profile과 @ActiveProfile (0) | 2025.02.20 |