Spring은 마법같다.🪄 너무나도 사용하기 쉽다.
대신 모르면 잘못 사용해서 문제를 일으키기도 쉽다는 함정이 있다.
3줄 요약 간단 흐름🐳
- 트랜잭션은 하나의
Connection을 가져와 사용하다가 닫는 사이에 발생합니다.- 쉽게말하면
commitorrollback이 호출되기 전까지 하나의 트랜젝션으로 묶인다.Exception이 발생하면rollback을 수행하고, 정상적으로 로직을 수행하면commit이 이루어진다.
@Transactional는 public함수에서만 작동한다.try-catch문을 사용하면 catch문에 rollback 동작을 위임하기 때문에 rollback이 발생하지 않는다.@Transactional은 전파된다. 부모 메서드에 트랜젝션이 달려있다면 종속된다. RuntimeException 계열의 예외는 기본적으로 호출 스택을 따라 최상위까지 전파된다. REQUIRES_NEW로 생성된 트랜잭션에서 예외가 발생하면, 상위 트랜잭션으로 전파되어 상위 트랜잭션까지 롤백된다.IOException, NullPointException과 같은 예외를 말한다.Checked exception의 경우 try-catch 블록으로 감싸서 처리해야 하지만, 처리하지 않고 그대로 전파하면 상위 트랜잭션에도 영향을 미칠 수 있다.@Transactional(rollbackFor = FileNotFoundException.class)와같이 rollbackFor을 지정해줘야한다.REQUIRES_NEW로 시작한 트랜젝션도 현재 트랜젝션과 동일한 스레드에서 진행된다.REQUIRES_NEW에서 발생한 예외는 이를 호출한 트랜젝션에 전파가 된다.REQUIRES_NEW옵션을 사용하더라도 같은 class내의 함수를 호출할 경우 하나의 트랜젝션으로 간주한다.아래는
REQUIRES_NEW를 이용하여 다룬 예시 코드이다.(공통 조건)
- 부모는 기본옵션, 자식은
REQUIRES_NEW옵션을 주었다.UneckedException을 발생시킨다.- 부모와 자식 메서드는 다른
class이다.부모쪽 코드 (ManagerService class)
@Transactional public void parentMethod() { managerLogService.save("parent"); managerLogService.childMethod(); }자식 코드 (ManagerLogService class)
@Transactional(propagation = Propagation.REQUIRES_NEW) public void childMethod() throws IOException { save("child"); } public void save(String msg) { ManagerLog log = ManagerLog.builder() .result(msg) .build(); managerLogRepository.save(log); }
try-catch문이 없다.Exception이 발생한다.부모 코드
@Transactional
public void parentMethod() {
managerLogService.save("parent");
managerLogService.childMethod();
}
자식 코드
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
save("child");
throw new InvalidRequestException("error");
}
자식 메서드에서 예외가 발생하여 롤백된다.
부모 메서드에도 예외가 전파되어 롤백된다.
try-catch문이 없다.Exception이 발생한다.부모 코드
@Transactional
public void parentMethod() {
managerLogService.save("parent");
managerLogService.childMethod();
throw new InvalidRequestException("error");
}
자식 코드
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
save("child");
}
자식 메서드는 정상처리되어 커밋된다.
부모 메서드는 예외가 발생하여 롤백된다.
try-catch가 있다.Exception이 발생한다.부모 코드
@Transactional
public void parentMethod() {
managerLogService.save("parent");
managerLogService.childMethod();
}
자식 코드
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
try{
save("child");
throw new InvalidRequestException("error");
} catch (InvalidRequestException ex) {
System.out.println(ex.getMessage());
}
}
자식 메서드에 catch문이 있으므로 정상 처리되어 커밋된다.
부모 메서드도 커밋된다.
try-catch가 있다.Exception이 발생한다.부모 코드
@Transactional
public void parentMethod() {
try {
managerLogService.save("parent");
managerLogService.childMethod();
}catch (InvalidRequestException ex) {
System.out.println(ex.getMessage());
}
}
자식 코드
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
save("child");
throw new InvalidRequestException("error");
}
자식 메서드는 예외가 발생하여 롤백된다.
부모 메서드는 catch로 잡아 정상커밋된다.
try-catch가 있다.Exception이 발생한다.부모 코드
@Transactional
public void parentMethod() {
try {
managerLogService.save("parent");
managerLogService.childMethod();
throw new InvalidRequestException("error");
}catch (InvalidRequestException ex) {
System.out.println(ex.getMessage());
}
}
자식 코드
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
save("child");
}
자식 메서드는 정상처리되어 커밋된다.
부모 메서드에서 예외가 발생했지만 catch문이 있으므로 정상 처리되어 커밋된다.