스프링 트랜잭션 이해

2023. 4. 6. 22:59카테고리 없음

 

@Transactional을 통해 선언적 트랜잭션 방식을 사용시 

어노테이션 하나로 트랜잭션을 적용 할 수 있다.

이 기능은 트랜잭션 관련 코드가 눈에 보이지 않고 AOP 기반으로 동작하기에 실제 트랜잭션이 적용 되고 있는지 확인하기 어렵다.

 

 

스프링 트랜잭션이  실제 적용되고 있는지 확인 하는 방법 

 

@Transactional 을메서드나 클래스에 붙이면 해당 객체는 트랜잭션 AOP 적용의 대상이다 .

실제 객체 대신에 트랜잭션을 처리해주는 프록시 객체가 스프링 빈에 등록된다 , 그리고 주입을 받을 때도 실제 객체 대신에 프록시 객체가 주입된다.

프록시 클래스의 이름 출력

스프링 컨테이너에 트랜잭션 프록시 등록 

 

  1. @Transactional 어노테이션이 메서드에 하나라도 있으면 트랜잭션 AOP는 프록시를 만들어서 스프링 컨테이너에 등록 
  2. 실제 basicService 객체 대신 프록시 객체를 스프링 빈에 등록한다 , 프록시는 실제 객체를 참조한다 ,
  3. 클라이언트인 txBasicTest 는 스프링 컨테이너에 @Autowired BasicService basicService 로 의존관계 주입을 요청한다. 스프링 컨테이너에는 실제 객체 대신에 프록시가 스프링 빈으로 등록되어 있기 때문에 프록시를 주입한다.
  4. BasicService 대신에 프록시인 객체가 주입된다.

 

 

 

1) 프록시 호출 

2)스프링 컨테이너를 통해 트랜잭션 매니저 획득

3)transactionManager.getTransaction()

4)데이터 소스 -> 커넥션 생성

5)con.setAutoCommit(false) 

6)커넥션 보관

7)트랜잭션 동기화 매니저 에서 (보관된 커넥션)

8)실제 서비스 호출

9) 트랜잭션 동기화 커넥션 획득 

 

 

 

 

 

트랜잭션 옵션 

-value, transactionManager

트랜잭션을 사용할려면 스프링 빈에 등록된 트랜잭션 매니저를 선택해야 한다 .

트랜잭션 매니저가 커넥션 풀에서 커넥션을 얻어와 트랜잭션을 시작하고 끝낸뒤 다시 커넥션 풀에 반환한다.

 

 예시)

@Transactional("memberTxManager")

 

-rollbackFor

예외 발생시 스프링 트랜잭션의 기본 정책은 다음과 같ㅌ다

언체크 예외 RuntimeException,Error 와 그 하위 예외가 발생하면 롤백한다.

체크 예외인 Exception과 그하위 예외들은 커밋한다.

 

예시)

@Transactional(rollbackFor = Exception.class)

 

->옵션을 사용하면 어떤 예외가 발생할때 롤백할지 지정 할수 있다.

 

-norollbackFor

기본 정책에 추가로 어떤 예외가 발생했을때 롤백하면 안되는지 지정할 수 있다.

 

-isolation

트랜잭션 격리 수준 지정 , 대부분 데이터 베이스에서 설정한 기준을 따른다 .

 

DEFAULT : 데이터베이스에서 설정한 격리 수준을 따른다.

READ_UNCOMMITTED : 커밋되지 않은 읽기

READ_COMMITTED : 커밋된 읽기

REPEATABLE_READ : 반복 가능한 읽기

SERIALIZABLE : 직렬화 가능

 

readOnly

트랜잭션은 기본적으로 읽기 쓰기가 모두 가능한 트랜잭션이 생성된다. readOnly=true 옵션을 사용하면 읽기 전용 트랜잭션이 생성된다. 이 경우 등록, 수정, 삭제가 안되고 읽기 기능만 작동한다. (드라이버나 데이터베이스에 따라 정상 동작하지 않는 경우도 있다.) 그리고 readOnly 옵션을 사용하면 읽기에서 다양한 성능 최적화가 발생할 수 있다

 

옵션은 크게 3 곳에서 적용된다 .

  • 프레임 워크 
    JdbcTemplate은 읽기 전용 트랜잭션 안에서 변경 기능을 실행하면 예외를 던진다. JPA(하이버네이트)는 읽기 전용 트랜잭션의 경우 커밋 시점에 플러시를 호출하지 않는다. 읽기 전용이니 변경에 사용되는 플러시를 호출할 필요가 없다. 추가로 변경이 필요 없으니 변경 감지를 위한 스냅샷 객체도 생성하지 않는다. 이렇게 JPA에서는 다양한 최적화가 발생한다.
  • JDBC 드라이버
    읽기 전용 트랜잭션에서 변경 쿼리가 발생하면 예외를 던진다. 읽기, 쓰기(마스터, 슬레이브) 데이터베이스를 구분해서 요청한다. 읽기 전용 트랜잭션의 경우 읽기 (슬레이브) 데이터베이스의 커넥션을 획득해서 사용한다.
  • 데이터베이스
    데이터베이스에 따라 읽기 전용 트랜잭션의 경우 읽기만 하면 되므로, 내부에서 성능 최적화가 발생한다.

 

트랜잭션 AOP는 예외의 종류에 따라 트랜잭션을 커밋하거나 롤백한다.

  • 언체크 예외인 RuntimeException , Error 와 그 하위 예외가 발생하면 트랜잭션을 롤백한다.
  • 체크 예외인 Exception 과 그 하위 예외가 발생하면 트랜잭션을 커밋한다.
  • 정상 응답(리턴)하면 트랜잭션을 커밋한다.

1)runtimeException()실행시

Rolling back  -> 트랜잭션이 롤백 된다 

 

2)checkedException() 실행

MyException 은 Exception 을 상속받은 체크 예외이다. 따라서 예외가 발생해도 트랜잭션이 커밋된다.

 

3)rollbackFor() 실행 - 체크 예외를 강제로 롤백

rollbackFor = MyException.class 을 지정했기 때문에 MyException 이 발생하면 체크 예외이지만 트랜잭션이 롤백된다.

 

 

비즈니스 요구사항

 

주문을 하는데 상황에 따라 다음과 같이 조치한다.

1. 정상: 주문시 결제를 성공하면 주문 데이터를 저장하고 결제 상태를 완료 로 처리한다.

2. 시스템 예외: 주문시 내부에 복구 불가능한 예외가 발생하면 전체 데이터를 롤백한다.

3. 비즈니스 예외: 주문시 결제 잔고가 부족하면 주문 데이터를 저장하고, 결제 상태를 대기 로 처리한다. 이 경우 고객에게 잔고 부족을 알리고 별도의 계좌로 입금하도록 안내한다.

결제 잔고 부족시 비즈니스예외  체크예외

 

JPA를 사용하여 Order엔티티만든다.
스프링 데이터 JPA를 사용한다 . ( CRUD자동으로 가능)
Service 로직 작성

-username에 따라 처리 프로세스를 다르게 진행

 

complete()

사용자 이름을 정상으로 설정했다.

모든 프로세스 정상 수행

 

runtimeException()

사용자 이름을 예외 로 설정했다. RuntimeException("시스템 예외") 이 발생한다.

런타임 예외로 롤백이 수행되었기 때문에 Order 데이터가 비어 있는 것을 확인할 수 있다.

 

bizException()

사용자 이름을 잔고부족 으로 설정했다.

NotEnoughMoneyException("잔고가 부족합니다") 이 발생한다.

체크 예외로 커밋이 수행되었기 때문에 Order 데이터가 저장된다.

다음을 통해서 데이터가 대기 상태로 잘 저장 되었는지 검증한다.

assertThat(findOrder.getPayStatus()).isEqualTo("대기");

 

런타임 예외는 항상 롤백된다. 체크 예외의 경우 rollbackFor 옵션을 사용해서 비즈니스 상황에 따라서 커밋과 롤백을 선택하면 된다.

참고: 김영한 스프링 강의 

https://sup2is.github.io/2021/03/04/java-exceptions-and-spring-transactional.html