@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