@Service
@RequiredArgsConstructor
public class TransactionService {
private final PostRepository postRepository;
private final EntityManager em;
public void runPersist(Post post) {
transactionalPersistSave(post);
}
public void runRepository(Post post) {
transactionalRepositorySave(post);
}
@Transactional
public void transactionalPersistSave(Post post) {
em.persist(post);
System.out.println("영속화 여부: " + em.contains(post));
}
@Transactional
public void transactionalRepositorySave(Post post) {
postRepository.save(post);
System.out.println("영속화 여부: " + em.contains(post));
}
}
하나의 메소드라도 @Transactional
이 붙어있다면 스프링 컨테이너는 시작할 때 해당 클래스의 프록시 객체를 생성한다고 @Transactional과 Proxy객체 주입에서 설명했었다.
그렇다면 그 Proxy 객체는 어떻게 생겼으며, 어떻게 Transaction이 작동하는건지 한번 알아보자.
public class TransactionServiceProxy extends TransactionService {
private final PlatformTransactionManager transactionManager;
public TransactionServiceProxy(PostRepository postRepository, EntityManager em) {
super(postRepository, em);
this.transactionManager = new DataSourceTransactionManager();
}
@Override
public void runPersist(Post post) {
super.runPersist(post);
}
@Override
public void runRepository(Post post) {
super.runRepository(post);
}
@Override
public void transactionalPersistSave(Post post) {
// getTransaction()을 하면 트랜잭션이 시작된다.
TransactionStatus status
= transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
super.transactionalPersistSave(post);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
@Override
public void transactionalRepositorySave(Post post) {
TransactionStatus status
= transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
super.transactionalRepositorySave(post);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
}
}
TransactionService
를 감싸는 Proxy객체는 아마 위와 같지 않을까 생각이 든다.
트랜잭션 생성부분이 정확하지는 않지만 그래도 최대한 자료를 참고해서 작성해봤다.
중요한 부분은 @Transactional
이 붙은 메소드만 트랜잭션이 생성되고 커밋 또는 롤백이
되는 코드가 있다는 것이다.
runPersist
나 runRepository
는 @Transactional
이 안붙었으므로 트랜잭션이 생성되는 코드가 없음을 알 수 있다.
@SpringBootTest
public class TransactionalTest {
@Autowired TransactionService transactionService;
@Test
@DisplayName("transactionalPersistSave 테스트")
public void transactionalSaveTest() throws Exception {
Post post = new Post("test_post");
transactionService.transactionalPersistSave(post); // (1)
}
}
이제 위의 코드를 분석해보자.
transactionService
에는 TransactionServiceProxy
객체가 주입된다.(1)
코드에서 TransactionServiceProxy
의 transactionalPersistSave
가 실행된다.TransactionService
의 transactionalPersistSave
가 실행된다.그런데 다음 코드를 보자
@Test
@DisplayName("runPersist 메소드를 호출해도 트랜잭션을 탈 것인가")
public void runPersistTest() throws Exception {
Post post = new Post("test_post");
transactionService.runPersist(post); // (1)
}
위의 코드를 실행하면 트랜잭션이 없으므로 실행할 수 없다는 예외가 터진다.
아니 runPersist는 transactionalPersistSave를 실행시키는거고
transactionalPersistSave에는 @Transactional이 붙어있는데 왜 트랜잭션이 생성안되지?
이렇게 생각할 수 있다. 이에 대해서 자세히 답변을 하자면
(1)
코드에서 TransactionServiceProxy
의 runPersist
가 실행된다.runPersist
에서는 트랜잭션을 생성하는 코드가 없다.TransactionService
의 runPersist
가 실행된다.TransactionService
의 transactionalPersistSave
가 실행된다.이 순서대로 코드를 쭉 훑어보기를 바란다.
그 어디에도 트랜잭션을 생성하는 코드가 없음을 발견할 수 있다.
따라서 저 위의 테스트 코드를 실행하면 트랜잭션이 없다는 예외가 발생하는 것이다.
@Test
@DisplayName("runRepository 메소드를 호출해도 트랜잭션을 탈 것인가")
public void runRepositoryTest() throws Exception {
Post post = new Post("test_post");
transactionService.runRepository(post); // (1)
}
지금까지 쓴 글에 의하면 위의 코드도 트랜잭션이 없다는 예외가 발생해야 한다.
그런데 막상 실행하면 예외가 발생하지 않음을 알 수 있다.
응?? 그렇다면 트랜잭션이 생성이 되었다는 건데 어떻게 된건지 한번 알아보자.
(1)
코드에서 TransactionServiceProxy
의 runRepository
실행된다.TransactionService
의 runRepository
가 실행된다.TransactionService
의 transactionalRepositorySave
가 실행된다.public class TransactionService {
private final PostRepository postRepository;
@Transactional
public void transactionalRepositorySave(Post post) {
postRepository.save(post); // (2)
System.out.println("영속화 여부: " + em.contains(post)); // (3)
}
}
여기까지는 아직 트랜잭션이 생성되는 코드가 없다.
그런데 postRepository
의 save
메소드를 살펴보자.
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
@Transactional
이 붙은 것이 보이는가?
따라서 TransactionService
의 postRepository
는 PostRepositoryProxy
객체가 주입이 된다.
(2)
코드에서 postRepository
는 PostRepositoryProxy
객체이다.PostRepositoryProxy
의 save
가 실행될 때 비로소 트랜잭션이 실행되는 것이다.PostRepositoryProxy
의 save
가 종료되면 트랜잭션이 종료된다.(3)
코드의 영속화 여부는 트랜잭션이 종료된 후이므로 false
가 나오게 된다.