글 쓰게된 계기
'eunniverse씨, 이것 좀 대응해줘!', 팀장님의 다급한 호출!
하던 일을 멈추고 후다닥 보는데, API를 호출할 때 파라미터가 넘어가지 않았다.
분명 DB에 데이터를 INSERT 하고, API를 호출했는데.. 왜!!!! 안넘어갈까?
고민하던 와중 눈에 보였던
@Transactional 어노테이션!!!!
그렇다.. 결론은 @Transactional 설정 문제 였다.
분명 알고 있는 개념임에도 막상 찾으려니까 헤매였던 경험을 토대로 글을 써야지 싶어서 적는 글 -_-!
그래서 정확한 문제는?
// 데이터 추가
@Transactional(rollbackFor = Exception.class)
public void insertObject(Data data) {
try {
dao.insertObject(data);
....
this.updateObject(data);
callAPI();
} catch(Exception e) {
....
}
}
// 데이터 업데이트
@Transactional(rollbackFor = Exception.class)
public void updateObject(Data data) {
try {
....
} catch(Exception e) {
....
}
}
// API 호출 메서드
public void callAPI() {
try {
ResponseObject res = this.httpPost();
} catch(Exception e) {
log.error(e.getMessage());
}
}
@transactional이 걸려있기 때문에 데이터가 업데이트되지 않은 채로 api를 호출해서 오류가 발생한 것이었다.
그래서 어떻게 해결했나?
@transactional 에 있는 propagation 속성의 REQUIRED_NEW 를 사용했다.
transactional propagation 즉,
트랜잭션 전파 는 무엇인가??
트랜잭션 전파 의미
트랜잭션을 시작하거나, 기존 트랜잭션에 참여하는 방법을 결정하는 속성이다. 트랜잭션 경계의 시작 지점에서 트랜잭션 전파 속성을 참조해서 해당 범위의 트랜잭션을 어떤 식으로 진행시킬지 결정할 수 있다.
트랜잭션 전파 속성
1. REQUIRED
Defualt 속성이며, 모든 트랜잭션 매니저가 지원한다. 보통 이 속성으로 많이 사용한다. 미리 시작된 트랜잭션이 있으면 참여하고, 없으면 트랜잭션을 생성한다. 간단한 트랜잭션 전파 방식이지만 사용해보면 매우 강력하고 유용한 속성이라는 사실을 알 수 있다.
2. SUPPORTS
이미 시작된 트랜잭션이 있으면 참여하고 없으면 트랜잭션 없이 진행한다. 트랜잭션이 없긴 하지만 해당 경계 안에서 Connection이나 Hibernate Session 등을 공유할 수 있다.
3. MANDATORY
REQUIRED와 비슷하며, 이미 시작된 트랜잭션이 있으면 참여한다. 하지만 트랜잭션이 없다면 생성하는 것이 아니라 예외를 발생시킨다. 혼자서 독립적으로 트랜잭션을 실행하면 안되는 경우에 사용한다.
4. NOT_SUPPORTED
트랜잭션을 사용하지 않게 한다. 이미 진행 중인 트랜잭션이 있으면 보류시킨다.
5. REQUIRES_NEW
항상 새로운 트랜잭션을 시작한다. 이미 진행 중인 트랜잭션이 있다면, 트랜잭션을 보류시킨다.
6. NEVER
트랜잭션을 사용하지 않도록 강제한다. 이미 진행 중인 트랜잭션도 존재하면 안되며, 트랜잭션이 있다면 예외를 발생시킨다.
데이터 추가 -> 업데이트된 상태에서 API 호출이 되어야했기 때문에 transaction 순서 변경이 필요했다. 그래서 'REQUIRES_NEW' 를 지정해주어 매번 새로운 트랜잭션이 시작되게끔 처리했고, 의도한 순서대로 처리가 되었다!
(부록) @Transactional이 뭐길래?
의미
spring 에서는 어노테이션 방식으로 transaction을 지원한다.
클래스 또는 메서드 위에 @Transactional을 붙이면, 트랜잭션 기능이 적용된 프록시 객체가 생성되며, 트랜잭션 성공 여부에 따라 Commit 또는 Rollback 작업이 이루어진다.
동작원리
import org.springframework.transaction.annotation.Transactional;
import 문을 보면 알 수 있듯이 트랜잭션은 어노테이션 기반 AOP를 통해 구현되어있다.
따라서, 가지게 되는 특징은 다음과 같다.
1. 클래스, 메소드에 @Transactional이 선언되면 트랜잭션이 적용된 프록시 객체 생성
2. 프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우, 트랜잭션 시작
3. 예외가 없을 때는 Commit 예외가 발생하면 Rollback 진행