@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개의 댓글

관련 채용 정보