@Transactional(readOnly=true)
@Transactional(readOnly=true)
public String getData(Request request) {
// 조회 biz. 로직
Response response = XXXRepository.select(request);
}
@Transactional(readOnly=true)
어노테이션으로 SELECT
성능을 최적화 시킬 수 있다.@Transactional
또는 @Transactional(readOnly=false)
@Transactional
public void cudData(Request Request) {
// 삽입/수정/삭제 biz. 로직
XXXRepository.insert(request); // A
XXXRepository.update(request); // B
XXXRepository.delete(request); // C
}
@Transactional
어노테이션의 default는 readOnly=false
이다.A
, B
는 성공하였지만 C
에서 익셉션이 발생한 경우, A
, B
동작 모두 Rollback 된다. (DB에 반영되지 않음)만약...
@Transactional(readOnly=true)
public String getData() {
// 조회 biz. 로직
...
Repository.insert(DTO); // INSERT - Exception 발생
}
@Transactional(readOnly=true)
어노테이션이 붙어있는 클래스 또는 메소드 내에서 INSERT
, UPDATE
, DELETE
를 실행할 경우 예외가 발생한다.@Transactional
public void cudData(Request Request) {
// 삽입/수정/삭제 biz. 로직
try {
XXXRepository.insert(request); // A
XXXRepository.update(request); // B
XXXRepository.delete(request); // C
} catch (Exception e) {
System.out.println("[Error] A, B, C 로직 처리중 에러가 발생하였습니다."); // 예외를 던지지 않는 구문
}
}
Q1) 만약 위처럼 트랜잭션@Transactional
과 try~catch
구문을 같이 사용한다면??
A1) @Transactional
에 의한 트랜잭션 처리는 동작하지 않는다.
각각의 서비스 A
, B
, C
에서 발생하는 모든 예외(예외의 최상위 계층인 Exception을 catch함)를 try~catch
문으로 처리하도록 정의했기 때문에, 발생하는 모든 예외는 catch
블록에서 처리된다.
@Transactional
에 의한 트랜잭션 처리 루틴으로 예외는 전파되지 않아 @Transactinal
은 아무런 의미를 가지지 않게 된다.
즉, 일부 서비스 예외에 의한 롤백(Rollback)은 절대 일어나지 않는다.
Q2) 그래도 @Transactional
과 try~catch
문을 같이 사용하고자 한다면?
A2) catch
문에라도 Exception을 발동 시켜야 한다.
@Transactional
public void cudData(Request Request) {
// 삽입/수정/삭제 biz. 로직
try {
XXXRepository.insert(request); // A
XXXRepository.update(request); // B
XXXRepository.delete(request); // C
} catch (Exception e) {
throw new Exception(); // 예외를 던지는 구문
}
}
@Transactional // private 접근제어자에 트랜잭션 정책 적용 안됨
private String method1() { // IntelliJ 에서는 컴파일 에러 발생
...
}
@Transactional // public 접근제어자에 트랜잭션 정책 적용 됨
public String method2() {
...
}
private
접근제어자 메소드에 @Transactional
을 붙여도 스프링이 트랜잭션을 관리해 주지 않는다. @Transactional
어노테이션을 non-public (protected, private, package, no-modifier)
메소드에 적용할 경우에 런타임 에러는 발생하지 않지만, 트랜잭션 정책이 적용되지 않는다. (IntelliJ 에서는 컴파일 에러를 뱉음)@Transactional
어노테이션은 public
메소드에 사용!@Transactional
public Result biz(Dto dto) {
// 1. UPDATE 수행
this.update(dto); // SUCCESS
// 2. INSERT 수행
this.insert(dto); // Exception 발생
}
private void update(Dto dto) throws Exception {
Dao.update(dto); // SUCCESS
}
private void insert(Dto dto) throws Exception {
Dao.insert(dto); // Exception 발생
}
하나의 API 호출에서 1.UPDATE
, 2.INSERT
동작이 모두 COMMIT
되어야 운영상 이슈가 없는 상황을 가정해본다면... (둘 중 하나만 COMMIT
되면 운영 이슈)
1) 예시 코드와 같이 @Transactional
어노테이션으로 1.UPDATE
와 2.INSERT
를 하나의 트랜잭션으로 묶었을 때,
만약, 1.UPDATE
동작 정상 수행 후 2.INSERT
동작에서 익셉션이 발생한다면 1.UPDATE
결과도 DB에 COMMIT
되지 않는다. 1.UPDATE
, 2.INSERT
모두 정상 동작해야 하나의 트랜잭션에서 COMMIT
됨
예외를 발생시키면, ROLLBACK
처리를 자동 수행해준다.
2) @Transactional
어노테이션으로 하나의 트랜잭션으로 묶지 않았을 때,
만약, 1.UPDATE
동작 정상 수행 후 2.INSERT
동작에서 익셉션이 발생한다면 1.UPDATE
결과만 COMMIT
되어 운영 이슈 발생. ex) 결제 시스템 등..
이처럼 하나의 COMMIT/ROLLBACK
단위(?)로 트랜잭션으로 묶기 위해 사용하며...
사실 @Transactional
어노테이션의 기능은 더 많지만.. 간단히 정리해 보았다.
1) Propagation.REQUIRED
(Default)
@Transactional(propagation = Propagation.REQUIRED)
public void test() { ... }
Caller/Callee
에 모두 전파되어 롤백된다. (하나의 트랜잭션)Propagation.REQUIRED
는 디폴트이기에 생략해도 된다.2) Propagation.REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test() { ... }
Propagation.REQUIRES_NEW
는 매번 새로운 트랜잭션을 시작한다. (새로운 연결을 생성하고 실행) 3) Propagation.NESTED
@Transactional(propagation = Propagation.NESTED)
public void test() { ... }
Propagation.NESTED
는 Propagation.REQUIRED
와 동일하게 작동하나 SAVEPOINT
를 지정한 시점까지 부분 롤백이 가능하다는 차이점이 있다.SAVEPOINT
기능을 지원해야 사용이 가능하다. (대표적으로 Oracle)Transactional 전파 레벨 예제
혹시, 틀린 내용이 있다면, 댓글 주시면 감사드리겠습니다. 🙇🏻♂️
https://rlawls1991.tistory.com/entry/spring-transaction-roll-back-%EC%B2%98%EB%A6%AC