스프링 JPA를 사용하던 도중 한가지 궁금한 점이 생겼다.
@Transactional
어노테이션이 붙어있는 메서드에서@Transactional
이 들어간 다른 메서드를 호출하면 어떻게 될까?
예를 들면, 아래와 같은 상황이다.
@Transactional
public Todo addNewTodo(String description){
Todo newTodo = new Todo(description);
return saveTodo(newTodo)
}
@Transactional
public Todo saveTodo(Todo todo){
todoRepository.save(todo);
return todo;
}
addNewTodo
라는 메서드에서 saveTodo
라는 메서드를 호출하고 있는데, 두 메서드 모두 @Transactional
어노테이션이 붙어 있는 상태이다.
스프링에서는 위 상황과 같이 트랜잭션이 중첩되어 실행되는 경우 어떻게 처리할지에 대한 설정을 제공하고 있다.
이를 트랜잭션 전파 설정이라고 한다.
즉, 부모 트랜잭션과 자식 트랜잭션 사이의 롤백 여부는 서로 독립적이다.
REQURIRES_NEW
설정과 비슷하지만, 부모 트랜잭션과 자식 트랜잭션의 롤백 여부가 서로 종속적이라는 점이 다르다.
트랜잭션이 반드시 필요하지만 자식 트랜잭션(중첩 트랜잭션)을 생성하지는 않는 경우에 사용한다.
즉, 트랜잭션을 절대 적용해서는 안되는 경우에 사용한다.
아래 코드는 위쪽 예시에 나온 메서드를 하나의 클래스로 묶어 놓은 것이다.
@Service
public class TodoAdder{
@Transactional
public Todo addNewTodo(String description){
Todo newTodo = new Todo(description);
return saveTodo(newTodo)
}
@Transactional(propagation = Propagation.NESTED)
public Todo saveTodo(Todo todo){
todoRepository.save(todo);
return todo;
}
}
트랜잭션 전파 설정은 @Transactional
어노테이션에 propagation
속성을 지정해 줌으로써 사용할 수 있다.
위 코드는 saveTodo
메서드에 NESTED 전파 설정을 사용하여 자식 트랜잭션을 생성하게 만든 모습이다.
다만, 위 코드를 실제로 실행해 보면 우리가 의도했던 바와는 달리 자식 트랜잭션이 생성되지 않고 addNewTodo의 트랜잭션만 생성된다.
그 이유는 두 메서드가 하나의 클래스로 묶여있기 때문이다.
스프링의 트랜잭션 기능은 스프링 AOP의 Proxy를 기반으로 작동한다. (스프링 Proxy에 대해서는 나중에 더 자세히 다뤄 보아야겠다.)
@Transactional
어노테이션이 붙은 메서드가 실행되는 경우, Spring AOP에서 프록시 객체를 만들어 해당 메서드 실행 전/후로 트랜잭션 시작/커밋을 하는 코드를 추가해 주는 방식으로 동작한다.
이때 Spring에서 @Transactional
어노테이션이 붙은 메서드의 호출을 가로챌 수 있어야 하는데, Spring AOP에서는 기본적으로 같은 객체 내부에서의 메서드 호출을 가로챌 수가 없다고 한다.
그래서 addNewTodo
메서드의 트랜잭션을 생성하고 saveTodo
를 호출했을 때 트랜잭션이 따로 생성되지 않는 것이다.
위 문제를 해결하는 가장 간단한 방법은 두 메서드를 서로 다른 두 클래스로 분리하는 것이다.
@Service
public class TodoAdder{
@Transactional
public Todo addNewTodo(String description){
Todo newTodo = new Todo(description);
return saveTodo(newTodo)
}
}
---------------------------------------------
@Service
public class TodoAdder2{
@Transactional(propagation = Propagation.NESTED)
public Todo saveTodo(Todo todo){
todoRepository.save(todo);
return todo;
}
}
https://stackoverflow.com/questions/53447687/transactional-propagation-requires-new-not-work