@Transactional과 transaction 작동여부

PPakSSam·2022년 1월 19일
0

@Transactional과 Proxy 목차


@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이 붙은 메소드만 트랜잭션이 생성되고 커밋 또는 롤백이
되는 코드가 있다는 것이다.
runPersistrunRepository@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)
    }
}

이제 위의 코드를 분석해보자.

  1. transactionService에는 TransactionServiceProxy객체가 주입된다.
  2. (1)코드에서 TransactionServiceProxytransactionalPersistSave가 실행된다.
  3. 트랜잭션이 생성된 다음 TransactionServicetransactionalPersistSave가 실행된다.
  4. 따라서 영속화가 잘 됨을 알 수 있다.

그런데 다음 코드를 보자

@Test
@DisplayName("runPersist 메소드를 호출해도 트랜잭션을 탈 것인가")
public void runPersistTest() throws Exception {

    Post post = new Post("test_post");
    transactionService.runPersist(post); // (1)
}

위의 코드를 실행하면 트랜잭션이 없으므로 실행할 수 없다는 예외가 터진다.

아니 runPersist는 transactionalPersistSave를 실행시키는거고
transactionalPersistSave에는 @Transactional이 붙어있는데 왜 트랜잭션이 생성안되지?

이렇게 생각할 수 있다. 이에 대해서 자세히 답변을 하자면

  1. (1)코드에서 TransactionServiceProxyrunPersist가 실행된다.
  2. runPersist에서는 트랜잭션을 생성하는 코드가 없다.
  3. TransactionServicerunPersist가 실행된다.
  4. TransactionServicetransactionalPersistSave가 실행된다.

이 순서대로 코드를 쭉 훑어보기를 바란다.
그 어디에도 트랜잭션을 생성하는 코드가 없음을 발견할 수 있다.
따라서 저 위의 테스트 코드를 실행하면 트랜잭션이 없다는 예외가 발생하는 것이다.

참고

@Test
@DisplayName("runRepository 메소드를 호출해도 트랜잭션을 탈 것인가")
public void runRepositoryTest() throws Exception {

    Post post = new Post("test_post");
    transactionService.runRepository(post); // (1)
}

지금까지 쓴 글에 의하면 위의 코드도 트랜잭션이 없다는 예외가 발생해야 한다.
그런데 막상 실행하면 예외가 발생하지 않음을 알 수 있다.
응?? 그렇다면 트랜잭션이 생성이 되었다는 건데 어떻게 된건지 한번 알아보자.

  1. (1)코드에서 TransactionServiceProxyrunRepository 실행된다.
  2. 여기서는 트랜잭션이 생성되는 코드는 없다.
  3. TransactionServicerunRepository가 실행된다.
  4. TransactionServicetransactionalRepositorySave가 실행된다.
public class TransactionService {

    private final PostRepository postRepository;
    
    @Transactional
    public void transactionalRepositorySave(Post post) {
        postRepository.save(post); // (2)
        System.out.println("영속화 여부: " + em.contains(post)); // (3)
    }
}

여기까지는 아직 트랜잭션이 생성되는 코드가 없다.
그런데 postRepositorysave 메소드를 살펴보자.

@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이 붙은 것이 보이는가?
따라서 TransactionServicepostRepositoryPostRepositoryProxy객체가 주입이 된다.

  1. (2) 코드에서 postRepositoryPostRepositoryProxy 객체이다.
  2. PostRepositoryProxysave가 실행될 때 비로소 트랜잭션이 실행되는 것이다.
  3. PostRepositoryProxysave가 종료되면 트랜잭션이 종료된다.
  4. 따라서 (3)코드의 영속화 여부는 트랜잭션이 종료된 후이므로 false가 나오게 된다.
profile
성장에 대한 경험을 공유하고픈 자발적 경험주의자

0개의 댓글