Unchecked Exception과 checked Exception의 트랜잭션의 동작차이
web/Spring

Unchecked Exception과 checked Exception의 트랜잭션의 동작차이

반응형

스프링에서 트랜잭션을 사용할 때 기본적으로 에러가 발생하면 롤백이 동작할 것처럼 기대할 수 있으나 사실 모든 exception에 대해서 롤백을 하지 않는다.

 

우선 unchecked excetion에 대해서 롤백이 동작하는지 확인해보자.

 

테스트 준비

@Getter
@Entity
@NoArgsConstructor
public class Company {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private long id;
    private String name;
    private String location;

    @Builder
    public Company(String name, String location) {
        this.name = name;
        this.location = location;
    }
}


public interface CompanyRepository extends JpaRepository<Company, Long> {
}

회사정보를 담고있는 Company 엔티티가 있고 name과 location 정보를 담을 수 있다. 테스트 초기 테이블은 아무런 데이터가 들어있지 않다.

 

테스트 1. RuntimeExcetion 발생 시 롤백이 되는가?

그럼 대표적으로 많이 사용하는 RuntimeException인 IllegalStateException을 발생시켜서 실제 company save 작업이 롤백이 되는지 확인해보자.

 

로직은 아주 간단하다. 입력된 파라미터값으로 테이블에 저장하고 마지막에 Runtime Exception을 발생시키고 실제 테이블에 결과를 확인해보자.

@Service
public class CompanyService {

    private final CompanyRepository companyRepository;

    public CompanyService(CompanyRepository companyRepository) {
        this.companyRepository = companyRepository;
    }

    @Transactional
    public void addCompany(String name, String location) {
        companyRepository.save(Company.builder()
                        .location(location)
                        .name(name)
                .build());

        throw new IllegalStateException();
    }

}

insert 입력 이후에 에러가 발생되었고 테이블에 데이터도 입력되지 않았다.

 

 

 

테스트 2. checked exception 발생 시 롤백이 되지 않고 정상적으로 테이블에 저장이 되는가?

비즈니스 로직에 의해서 발생하는 exception이라는 의미로 Exception을 상속한 Business 클래스를 하나 만들고 테스트 1에서 실행했던  RuntimeException자리에 해당 에러를 뱉도록 해보자.

public class BusinessException extends Exception {
}


@Service
public class CompanyService {

    private final CompanyRepository companyRepository;

    public CompanyService(CompanyRepository companyRepository) {
        this.companyRepository = companyRepository;
    }

    @Transactional
    public void addCompany(String name, String location) throws BusinessException {
        companyRepository.save(Company.builder()
                        .location(location)
                        .name(name)
                .build());

        throw new BusinessException();
    }

}

테스트 1과 동일하게 insert문이 실행되고 Business Exception이 실행된것을 확인할 수 있다. 그럼 company 테이블에는 정말 들어갔는지 확인해보자. 

기대했던대로 입력했던 데이터가 들어가 있는 것을 확인할 수 있다. 

그리고 이전 테스트가 정상적으로 롤백이 되었다는것을 의미하는 autoincrement 값이 1이 아닌 2로 시작되었다. autoincrement값은 롤백이 되었다고 해도 그 값이 줄지 않는다.

 

 

왜 그럴까?

신입, 경력 개발자면접에 들어가다보면 이런 질문에 정확하게 대답하지 못하는 분들이 많았다. 그 이유는 우리는 단순하게 @Transactional 애노테이션이 붙고 그 안에서 에러가 발생하며 롤백이 된다고 배웠다. 

 

하지만 스프링은 기본적으로 예외 비즈니스 로직에서 발생할 수 있는 exception에 경우 에러라고 보지 않고 그 이외의 RuntimeException에서 발생되는 exception을 에러라고 보는 기본적인 개념이 있기 때문인다. 

 

그렇기 때문에 Exception을 비지니스별 사례별로 만드는 경우가 많은데 이때 트랜잭션 내에서 사용할 Exception이라고 하면 RuntimeException인지 확인해봐야한다. 

 

만약 RuntimeException이 아니여도 롤백이 될 수 있도록 하려면 어떻게 해야할까? 아래 테스트를 진행해보자.

 

 

테스트 3. rollbackOn을 사용하여 checked exception이 롤백이 되도록 처리

transactional의 속성에 보면 rollbackOn이라는 속성이 있다. 이 속성의 주석을 살펴보면 transaction interceptor에 해당 exception은 롤백이 될 수 있도록 설정한다고 되어있다.

 

우리가 아다시피 스프링 transaction은 TransactionInterceptor 어드바이스를 통해서 프록시로 동작하게 되어있다. 그렇기 때문에 해당 TransactionInterceptor에 롤백이 가능한 예외를 명시해준다고 주석에 기재되어있는 것이다.

 

그럼 설정하고 다시한번 돌려보자.

@Service
public class CompanyService {

    private final CompanyRepository companyRepository;

    public CompanyService(CompanyRepository companyRepository) {
        this.companyRepository = companyRepository;
    }

    @Transactional(rollbackOn = BusinessException.class)
    public void addCompany(String name, String location) throws BusinessException {
        companyRepository.save(Company.builder()
                        .location(location)
                        .name(name)
                .build());

        throw new BusinessException();
    }

}

동일하게 insert 쿼리가 실행되고 BusinessException이 발생했다.

id의 값은 증가했지만 데이터가 들어가지 않았다. 롤백이 정상적으로 수행되었다. 

 

 

스프링 트랜잭션은 처음 느끼는 개념은 간단하지만 실제로 사용하다보면 isolation, propagation등 생각을 해야할 부분이 많다. 놓치는 부분이 없도록 다시 되돌아보자.

반응형