[Spring/DB] 예외 발생 시 트랜잭션은 commit될까, rollback될까?

2023. 11. 11. 08:00· Spring
목차
  1. 💋 인트로
  2. 💋 소스 코드
  3. 💋 예외 발생 시 트랜잭션은 커밋될까, 롤백될까?
  4. ✔️ Runtime Exception 발생 시 트랜잭션은 Rollback된다
  5. ✔️ Checked Exception 발생 시 트랜잭션은 commit된다
  6. ✔️ rollbackFor 옵션을 통해서 Checked Exception도 rollback할 수 있다.
  7. 💋 왜? CheckedException은 commit되는거지?
  8. 💋 결론
  9. 💋 참고자료
반응형

 

반응형

 

💋 인트로

안녕하세요. 우아한테크코스 5기 깃짱이라고 합니다. 

이번 포스팅에서는, 예외 발생 시 트랜잭션은 commit될까, rollback될지에 대해 알아보도록 하겠습니다. 

 

 

💋 트랜잭션과 관련된 로그를 볼 수 있도록 설정

 

별다른 설정이 없는 경우에, 아래와 같은 로그가 찍힙니다.

 

2023-11-06 23:48:04.980  INFO 10902 --- [	Test worker] s.springtx.exception.RollbackTest		: Starting RollbackTest using Java 17.0.7 on starlight.local with PID 10902 (started by gitchan in /Users/gitchan/Projects/gitchan-study/spring-db-2/springtx)
2023-11-06 23:48:04.981  INFO 10902 --- [	Test worker] s.springtx.exception.RollbackTest		: No active profile set, falling back to 1 default profile: "default"
2023-11-06 23:48:05.114  INFO 10902 --- [	Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-11-06 23:48:05.119  INFO 10902 --- [	Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 2 ms. Found 0 JPA repository interfaces.
2023-11-06 23:48:05.260  INFO 10902 --- [	Test worker] com.zaxxer.hikari.HikariDataSource	   : HikariPool-1 - Starting...
2023-11-06 23:48:05.337  INFO 10902 --- [	Test worker] com.zaxxer.hikari.HikariDataSource	   : HikariPool-1 - Start completed.
2023-11-06 23:48:05.365  INFO 10902 --- [	Test worker] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-11-06 23:48:05.388  INFO 10902 --- [	Test worker] org.hibernate.Version					: HHH000412: Hibernate ORM core version 5.6.15.Final
2023-11-06 23:48:05.458  INFO 10902 --- [	Test worker] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2023-11-06 23:48:05.500  INFO 10902 --- [	Test worker] org.hibernate.dialect.Dialect			: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2023-11-06 23:48:05.567  INFO 10902 --- [	Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator	   : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-11-06 23:48:05.572  INFO 10902 --- [	Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-11-06 23:48:05.636  INFO 10902 --- [	Test worker] s.springtx.exception.RollbackTest		: Started RollbackTest in 0.748 seconds (JVM running for 1.12)
2023-11-06 23:48:05.757  INFO 10902 --- [	Test worker] s.s.e.RollbackTest$RollbackService	   : call runtimeException

 

트랜잭션이 어떻게 처리되었는지 확인할 수가 없습니다.

아래 설정을 추가하면, 트랜잭션이 어떻게 처리되었는지 로그를 통해 확인할 수 있습니다.

 

applicaiton.properties

logging.level.org.springframework.transaction.interceptor=TRACE
logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
#JPA log
logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
logging.level.org.hibernate.resource.transaction=DEBUG

 

현재 테스트중인 프로젝트는 JPA를 사용하고 있기 때문에, 아래 두 줄의 설정만 추가해도 충분하지만, JDBC를 사용하고 있다면 두 번째 줄의 설정이 필요합니다.

 

위와 같은 설정을 추가하면, 트랜잭션이 커밋되었는지, 롤백되었는지 직접 확인할 수 있습니다.

로그에 이전과 달리 아래의 내용이 추가되었습니다.

 

 

2023-11-06 23:45:40.769 DEBUG 10730 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Creating new transaction with name [springtx.springtx.exception.RollbackTest$RollbackService.runtimeException]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2023-11-06 23:45:40.787 DEBUG 10730 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Opened new EntityManager [SessionImpl(1576509541<open>)] for JPA transaction
2023-11-06 23:45:40.788 DEBUG 10730 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@73dbe25]
2023-11-06 23:45:40.789 TRACE 10730 --- [	Test worker] o.s.t.i.TransactionInterceptor		   : Getting transaction for [springtx.springtx.exception.RollbackTest$RollbackService.runtimeException]

2023-11-06 23:45:40.792 TRACE 10730 --- [	Test worker] o.s.t.i.TransactionInterceptor		   : Completing transaction for [springtx.springtx.exception.RollbackTest$RollbackService.runtimeException] after exception: java.lang.RuntimeException
2023-11-06 23:45:40.792 DEBUG 10730 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Initiating transaction rollback
2023-11-06 23:45:40.792 DEBUG 10730 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Rolling back JPA transaction on EntityManager [SessionImpl(1576509541<open>)]
2023-11-06 23:45:40.793 DEBUG 10730 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Closing JPA EntityManager [SessionImpl(1576509541<open>)] after transaction

 

그러면, 예외와 트랜잭션의 상관관계에 대해서 이제 알아봅시다.

 

💋 소스 코드

 

RollbackService

  • runtimeException() : 런타임 예외 발생하는 메서드
  • checkedException(): 컴파일 예외 발생하는 메서드
  • rollbackFor(): 컴파일 예외가 발생하는데, rollbackFor 옵션을 추가한 메서드

 

@Slf4j
@Service
class RollbackService {

	@Transactional
	public void runtimeException() {
		log.info("call runtimeException");
		throw new RuntimeException();
	}

	@Transactional
	public void checkedException() throws CheckedException {
		log.info("call checkedException");
		throw new CheckedException();
	}

	@Transactional(rollbackFor = CheckedException.class)
	public void rollbackFor() throws CheckedException {
		log.info("call checkedException");
		throw new CheckedException();
	}
}

static class CheckedException extends Exception {
}
}

 

 

💋 예외 발생 시 트랜잭션은 커밋될까, 롤백될까?

✔️ Runtime Exception 발생 시 트랜잭션은 Rollback된다

 

테스트 코드를 통해서 런타임 예외를 발생시키는 rollbackService.runtimeException() 메서드를 호출했습니다.

 

@SpringBootTest
public class RollbackTest {

	@Autowired
	private RollbackService rollbackService;

	@Test
	void runtimeException() {
		assertThatThrownBy(() -> rollbackService.runtimeException())
				.isInstanceOf(RuntimeException.class);
	}
}

 

의존성 주입된 rollbackService는 프록시 객체이기 때문에, 트랜잭션과 관련된 로직이 AOP로 적용되어 있을 것이다. 이 트랜잭션은 어떻게 될까요?

 

@Transactional
public void runtimeException() {
	log.info("call runtimeException");
	throw new RuntimeException();
}

 

롤백되네요.

 

2023-11-07 00:04:51.360 TRACE 12083 --- [	Test worker] o.s.t.i.TransactionInterceptor		   : Getting transaction for [springtx.springtx.exception.RollbackTest$RollbackService.runtimeException]
2023-11-07 00:04:51.363  INFO 12083 --- [	Test worker] s.s.e.RollbackTest$RollbackService	   : call runtimeException
2023-11-07 00:04:51.363 TRACE 12083 --- [	Test worker] o.s.t.i.TransactionInterceptor		   : Completing transaction for [springtx.springtx.exception.RollbackTest$RollbackService.runtimeException] after exception: java.lang.RuntimeException
2023-11-07 00:04:51.363 DEBUG 12083 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Initiating transaction rollback
2023-11-07 00:04:51.363 DEBUG 12083 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Rolling back JPA transaction on EntityManager [SessionImpl(626562869<open>)]
2023-11-07 00:04:51.364 DEBUG 12083 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Closing JPA EntityManager

 

✔️ Checked Exception 발생 시 트랜잭션은 commit된다

 

@Test
void checkedException() {
	assertThatThrownBy(() -> rollbackService.checkedException())
			.isInstanceOf(CheckedException.class);
}

 

호출된 아래 메서드는 컴파일 예외를 발생시킵니다.

아래에서 CheckedException은 직접 정의한 커스텀 예외입니다.

 

@Transactional
public void checkedException() throws CheckedException {
	log.info("call checkedException");
	throw new CheckedException();
}

 

???

커밋됩니다.

 

2023-11-07 00:05:20.818 DEBUG 12130 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@775c4054]
2023-11-07 00:05:20.818 TRACE 12130 --- [	Test worker] o.s.t.i.TransactionInterceptor		   : Getting transaction for [springtx.springtx.exception.RollbackTest$RollbackService.checkedException]
2023-11-07 00:05:20.821  INFO 12130 --- [	Test worker] s.s.e.RollbackTest$RollbackService	   : call checkedException
2023-11-07 00:05:20.822 TRACE 12130 --- [	Test worker] o.s.t.i.TransactionInterceptor		   : Completing transaction for [springtx.springtx.exception.RollbackTest$RollbackService.checkedException] after exception: springtx.springtx.exception.RollbackTest$CheckedException
2023-11-07 00:05:20.822 DEBUG 12130 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Initiating transaction commit
2023-11-07 00:05:20.822 DEBUG 12130 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Committing JPA transaction on EntityManager [SessionImpl(626562869<open>)]
2023-11-07 00:05:20.822 DEBUG 12130 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Closing JPA EntityManager

 

✔️ rollbackFor 옵션을 통해서 Checked Exception도 rollback할 수 있다.

 

@Test
void rollbackFor() {
	assertThatThrownBy(() -> rollbackService.rollbackFor())
			.isInstanceOf(CheckedException.class);
}

 

아래와 같이, rollbackFor 옵션을 추가로 사용해서 CheckedException 발생 시에도 롤백하도록 설정하고 있습니다.

 

@Transactional(rollbackFor = CheckedException.class)
public void rollbackFor() throws CheckedException {
	log.info("call checkedException");
	throw new CheckedException();
}

 

이 경우에는, 설정한 내용과 동일하게 CheckedException 발생 시에도 롤백됩니다.

 

2023-11-07 00:07:36.896 TRACE 12280 --- [	Test worker] o.s.t.i.TransactionInterceptor		   : Getting transaction for [springtx.springtx.exception.RollbackTest$RollbackService.rollbackFor]
2023-11-07 00:07:36.900  INFO 12280 --- [	Test worker] s.s.e.RollbackTest$RollbackService	   : call checkedException
2023-11-07 00:07:36.900 TRACE 12280 --- [	Test worker] o.s.t.i.TransactionInterceptor		   : Completing transaction for [springtx.springtx.exception.RollbackTest$RollbackService.rollbackFor] after exception: springtx.springtx.exception.RollbackTest$CheckedException
2023-11-07 00:07:36.900 DEBUG 12280 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Initiating transaction rollback
2023-11-07 00:07:36.900 DEBUG 12280 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Rolling back JPA transaction on EntityManager [SessionImpl(464614109<open>)]
2023-11-07 00:07:36.901 DEBUG 12280 --- [	Test worker] o.s.orm.jpa.JpaTransactionManager		: Closing JPA EntityManager [SessionImpl(464614109<open>)] after transaction

 

💋 왜? CheckedException은 commit되는거지?

 

Exception이 발생했을 때, rollback된다는 것은 꽤나 쉽게 이해할 수 있었습니다.

그래서 Runtime Exception이 발생했을 때 rollback된다는 것도 나름 당연하게 받아들였는데…!

 

왜 어째서 Checked Exception은 commit되는 걸까요?

 

스프링은 기본적으로 Checked Exception은 비즈니스 의미가 있을 때 사용하고, Runtime Exception은 복구가 불가능한 예외라고 가정합니다.

 

그런데, 복구 불가능한 예외는 뭘까요?

  • 데이터베이스 접근이 안됨.
  • SQL문에 오류가 있음.
  • 네트워크 통신이 안됨.

이 경우에는, 예외를 잡았다고 하더라도 처리를 할 수가 없기 때문에 Runtime Exception으로 만들어서, 상위 계층으로 올려 고객님께 죄송하다는 말을 전달하는 편이 좋습니다.

 

그렇다면, 비즈니스 의미가 있는 예외는 뭘까요?

  • 고객이 결제를 하는데, 잔고가 부족해서 예외가 발생함.

 

결제 잔고가 부족한 것은 시스템에 문제가 있어서 발생하는 예외가 아닙니다. 오히려 시스템은 비즈니스의 규칙(잔고가 부족하면 결제를 못한다)에 충실하게 처리한 것입니다.

 

이 비즈니스 예외는 매우 중요하고, 반드시 처리해야 하는 경우가 많으므로 Checked Exception을 고려할 수 있습니다.

 

예외는 두 가지로 구분해서 생각해야 합니다.

  • 시스템에 정말로 장애가 있어서 발생하는 예외
  • 시스템은 문제가 없고, 비즈니스 상황에서 정상 플로우를 못하는 예외

 

결제할 때 잔고가 부족하다면, 주문에 대한 데이터를 저장하고 결제 상태를 대기로 처리하고, 고객에게 잔고 부족을 알리고 별도 계좌로 입금하도록 기획했다면 어떨까요?

오히려 예외 발생 시 롤백이 아니라, 정해진 기획대로 커밋을 해야 할 것입니다. 롤백하게 되면, 주문에 대한 데이터가 날아가기 때문에 커밋하게 된 것입니다.

 

💋 결론

 

잔고 부족 시 결제 보류 등 비즈니스 규칙 상의 예외 = 시스템에는 문제 X = 예외를 응답값처럼 사용 = 사용자에게 보고하고, 현 상태를 데이터베이스에 저장해야 할 수도 있음 = Checked Exception → 트랜잭션 COMMIT

 

비즈니스 상황 따라서 Checked Exception이더라도, ROLLBACK하고 싶다면 → rollbackFor 옵션을 사용하면 된다.

 

네트워크, 데이터베이스 커넥션 등에 문제 = 시스템 자체의 문제 = Runtime Exception → 트랜잭션 ROLLBACK

 

 

 

💋 참고자료

  • 인프런 김영한 강의

 

도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
비밀댓글과 메일을 통해 오는 개인적인 질문은 받지 않고 있습니다. 꼭 공개댓글로 남겨주세요!

 

반응형

'Spring' 카테고리의 다른 글

[Spring] 서버에 파일 저장하고 경로 가져오기: ServletContext resource [/src/main/xxx.pdf] cannot be resolved to URL because it does not exist  (0) 2024.02.24
[Spring] 스프링 설정파일에서 @Value로 API KEY 등 변수 가져오기  (0) 2024.02.08
[Spring/AOP] 프록시 객체가 아닌 내부 호출 시 트랜잭션이 적용되지 않는 이슈  (0) 2023.11.06
[Spring/DB] JDBC, SQL Mapper(JdbcTemplate, MyBatis), ORM(JPA - Hybernate): 차이점과 등장 배경, 장단점에 대하여  (2) 2023.06.16
[Spring] 빈 스코프(Scope): 싱글톤, 프로토타입, 웹 관련 스코프  (0) 2023.06.15
  1. 💋 인트로
  2. 💋 소스 코드
  3. 💋 예외 발생 시 트랜잭션은 커밋될까, 롤백될까?
  4. ✔️ Runtime Exception 발생 시 트랜잭션은 Rollback된다
  5. ✔️ Checked Exception 발생 시 트랜잭션은 commit된다
  6. ✔️ rollbackFor 옵션을 통해서 Checked Exception도 rollback할 수 있다.
  7. 💋 왜? CheckedException은 commit되는거지?
  8. 💋 결론
  9. 💋 참고자료
'Spring' 카테고리의 다른 글
  • [Spring] 서버에 파일 저장하고 경로 가져오기: ServletContext resource [/src/main/xxx.pdf] cannot be resolved to URL because it does not exist
  • [Spring] 스프링 설정파일에서 @Value로 API KEY 등 변수 가져오기
  • [Spring/AOP] 프록시 객체가 아닌 내부 호출 시 트랜잭션이 적용되지 않는 이슈
  • [Spring/DB] JDBC, SQL Mapper(JdbcTemplate, MyBatis), ORM(JPA - Hybernate): 차이점과 등장 배경, 장단점에 대하여
깃짱
깃짱
연새데학교 컴퓨터과학과 & 우아한테크코스 5기 백엔드 스타라이토 깃짱
반응형
깃짱
깃짱코딩
깃짱
전체
오늘
어제
  • 분류 전체보기
    • About. 깃짱
    • Weekly Momentum
      • 2024
    • PROJECT
      • AIGOYA LABS
      • Stamp Crush
      • Sunny Braille
    • 우아한테크코스5기
    • 회고+후기
    • Computer Science
      • Operating System
      • Computer Architecture
      • Network
      • Data Structure
      • Database
      • Algorithm
      • Automata
      • Data Privacy
      • Graphics
      • ETC
    • WEB
      • HTTP
      • Application
    • C, C++
    • JAVA
    • Spring
      • JPA
      • MVC
    • AI
    • MySQL
    • PostgreSQL
    • DevOps
      • AWS
      • 대규모 시스템 설계
    • frontend
      • HTML+CSS
    • NextJS
    • TEST
    • Industrial Engineering
    • Soft Skill
    • TIL
      • 2023
      • 2024
    • Linux
    • Git
    • IntelliJ
    • ETC
      • 日本語

블로그 메뉴

  • 홈
  • 깃허브

인기 글

최근 글

태그

  • 우테코
  • 우아한테크코스
  • 스트림
  • 레벨로그
  • 함수형프로그래밍
  • Composition
  • 상속
  • 조합
  • 컴포지션
  • TDD
  • 상속과조합
  • OOP
  • 람다와스트림
  • lamda
  • 람다
  • 우아한테크코스5기
  • Java
  • 예외
  • 우테코5기
  • Stream
hELLO · Designed By 정상우.v4.2.0
깃짱
[Spring/DB] 예외 발생 시 트랜잭션은 commit될까, rollback될까?
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.