728x90

선언적 트랜잭션과 트랜잭션 전파 속성

트랜잭션의 속성에서 트랜잭션 전파 속성은 매우 핵심적인 기능이다.

REQUIRED로 설정한다면 앞의 트랜잭션에 참여하기에 다양한 크기의 트랜잭션을 만들 수 있기 때문이다.

 

보통 update()와 같은 메서드들은 update 하나의 단위로 트랜잭션이 생성되지만, 다음과 같은 경우에는 참여하도록 만들어야 한다.

 

이렇게 일괄적으로 update를 수행하는 메서드가 있다면, 트랜잭션을 윗 단계인 updateScheduler()로 설정해서 하나만 에러가 발생하더라도 전체가 rollback되도록 만들어야 한다.

이런 경우에 트랜잭션 전파가 유용하게 사용된다.

 

이렇게 AOP를 통해 코드 외부에서 트랜잭션을 적용하는 것을 '선언적 트랜잭션'이라고 한다.

반대로 처음에 TransactionTemplate을 통해 코드 내부에서 트랜잭션을 적용하는 방법을 '프로그래밍에 의한 트랜잭션'이라고 한다.

 

트랜잭션 동기화와 테스트

    @Test
    public void transactionSync(){

        userService.deleteAll();

        userService.add(User.builder()
                .id(new Date().toString()).build());
        userService.add(User.builder()
                .id(new Date().toString()).build());
    }

이러한 테스트가 있다고 해보자.

service에는 각각의 메서드에 트랜잭션이 적용되기 때문에, 현재는 3개의 트랜잭션이 만들어질것이다.

현재 전파 속성은 REQUIRED이기 때문에, 해당 트랜잭션들을 하나로 묶을 수 있을 것 같다.

 

    @Test
    public void transactionSync(){

        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        TransactionStatus tx = transactionManager.getTransaction(transactionDefinition);

        userService.deleteAll();

        userService.add(User.builder()
                .id(new Date().toString()).build());
        userService.add(User.builder()
                .id(new Date().toString()).build());

        transactionManager.commit(tx);
    }

그냥 간단하게 묶어 보았다.

 

해당 메서드들이 트랜잭션에 참여했는지 확인해보기 위해, 잠깐 readOnly 속성을 추가해보았다.

이렇게 readOnly를 true로 설정하고 테스트를 수행해보니, 역시 다음과 같은 에러가 발생한다.

이로써 일단 트랜잭션 안으로 들어왔다는 것은 알 수 있었다.

 

이 방법을 통해, 테스트의 메서드에서 트랜잭션을 생성한 후 테스트 내에서 만들어지는 모든 트랜잭션을 참여하게 한 후 롤백하는 편리한 방법을 사용 할 수 있을 것 같다.

이렇게 테스트 내의 모든 DB 작업을 하나의 트랜잭션 안에서 동작하게 하고 무조건 롤백하는 방법을 '롤백 테스트'라고 한다고 한다.

 

    @Test
    public void transactionSync(){
    
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        
        try
        {
            userService.deleteAll();
            userService.add(User.builder()
                .id(new Date().toString()).build());
            userService.add(User.builder()
                .id(new Date().toString()).build());
        }
        finally
        {
            transactionManager.rollback(transactionStatus);
        }
    }

 

finally를 통해, 무조건 rollback하도록 만드는 것이다.

 

현재 테스트에서 deleteAll()을 사용하기에 기존의 데이터베이스에 큰 영향을 준다.

물론 테스트 데이터베이스에서 테스트를 수행하겠지만, 이런 테스트는 문제가 될 수 있기 때문에 기존의 데이터베이스를 유지하도록 해야한다.

그리고 보통은 데이터베이스에 데이터를 삽입한 후 일괄삭제하는 방법보다 롤백하는 작업이 더 빠르기에 테스트의 수행속도를 높일 수 있다.

 

테스트를 위한 트랜잭션 어노테이션

테스트에서도 @Transactional 어노테이션이 동작한다.

진짜로 동작하는지 확인해보기 위해, 한 번 적용해보도록 하자.

    @Test
    @Transactional(readOnly = true)
    public void transactionSync(){
        
        userService.deleteAll();

        userService.add(User.builder()
                .id(new Date().toString()).build());
        userService.add(User.builder()
                .id(new Date().toString()).build());
    }

이렇게 readOnly를 적용하고, 진짜로 only readOnly 에러가 발생하는지 확인해보자.

 

여기서도 @Transactional이 적용되는 것을 볼 수 있었다.

 

뭔가 이 방법을 통해 데이터베이스의 결과와 상관없이 무조건 rollback하는 어노테이션을 만들 수 있을 것 같다.

바로 @Rollback이다.

이름으로 바로 알 수 있지만, 해당 어노테이션은 기본적으로 트랜잭션을 강제 롤백한다.

 

바로 직접 확인해보도록 하자.

현재 데이터베이스의 데이터는 비어있다.

 

거기에 다음과 같은 테스트 메서드를 실행해보자.

    @Test
    @Rollback
    @Transactional
    public void addUser(){
        userService.add(
                User.builder()
                        .id(UUID.randomUUID().toString().substring(0, 5))
                        .name("test")
                        .password("password")
                        .build()
        );
    }

 

이렇게 하니 테스트가 끝나도 데이터가 추가되지 않은 것을 볼 수 있었다.

@Transactional을 붙여야만 롤백이 적용된다.

 

테스트용 데이터베이스에도 데이터를 지워야하는 경우가 많았는데, rollback을 활용해 데이터를 관리 할 수 있을 것 같다.

 

+ Recent posts