본문 바로가기
Framework/MyBatis

스프링에서 직접 트랜잭션 제어하기 : @Transactional

by 예스p 2023. 4. 19.

트랜잭션 수동제어

스프링 및 스프링부트는 Mybatis의 트랜잭션을 자동으로 처리해준다.

하지만 로직에 따라 직접 제어해야할 때가 있을 것이다. 


스프링에서의 트랜잭션

  • SqlSession
    - Mybatis의 클래스
    - DB와의 연결 및 SQL문 처리를 수행한다.
  • SqlSessionTemplate
    - Mybatis-Spring 연동 모듈 중 하나
    - SqlSession을 스프링의 IoC컨테이너에서 관리하도록 함으로써 객체 생성과 관리, 트랜잭션등을 처리한다.
    >> 장점 : 코드가 간결해지고 자원누수를 막는다.
    >> 단점 : .commit(), .rollback()을 쓸수 없다! (직접 트랜잭션 관리 불가)

 

 

@Transactional

  • 트랜잭션을 수동으로 제어하게끔 스프링에서 제공하는 어노테이션
  • 일반적으로 비즈니스 로직을 수행하는 서비스 계층에서 작성한다.
  • 어노테이션을 쓰지 않으면?
    하나의 SQL문을 실행할때 성공하면 commit되고, 오류발생하면 rollback된다.
  • 어노테이션을 쓰면?
    메소드 내에 존재하는 모든 SQL문은 하나의 트랜잭션에서 관리된다.

 

 

@Transactional 속성

  • propagation
    : 트랜잭션 전파 옵션을 정의
    ** 트랜잭션 전파 옵션이란? 트랜잭션에서 다른 트랜잭션을 호출할 때 어떠한 방식으로 처리할지 설정하는 것
  • timeout / timeoutString
    : 트랜잭션 제한 시간을 정의. 제한 시간을 초과하면 예외를 발생시킨다.
  • readOnly
    : 현재 트랜잭션이 읽기 전용인지 읽기-쓰기 전용인지 정의
  • rollbackFor / rollbackForClassName
    : 지정한 예외가 발생하면 롤백되도록 Throwable 클래스를 정의
  • noRollbackFor / noRollbackForClassName
    : Transactional 어노테이션을 적용한 메소드에서 또다른 Transactional 가 적용된 메소드를 호출하면, 호출된(callee) 트랜잭션은 호출한(callser) 트랜젝션에 병합된다. callee 트랜잭션을 수행하다가 에러가 발생했을 경우 모든 트랜잭션은 rollback처리된다.
    이때, 지정한 예외가 발생하더라도 롤백되지 않도록 상기 속성으로 Throwable 클래스를 정의

 

@Transactional 기본 롤백 조건

  • Runtime Exception
    - 개발자가 처리하기 어려운 예외. 프로그램 실행 중에 발생
    - 자동으로 롤백된다.
    - 롤백을 원하지 않으면 try/catch 등으로 예외처리를 해준다.
  • Error
    - Exception이 아닌 경우로 시스템 메모리 부족처럼 예측 및 처리가 어렵다.
    - 자동으로 롤백된다.
  • Checked Exception
    - 프로그램이 제어할 수 없지만 개발자가 충분히 처리 가능한 예외
       ex) SQLException, ...
    - 개발자가 직접 처리할 수 있기 때문에 자동 롤백 대상이 아님
    - 롤백 필요시 rollbackFor 속성을 기재해야한다.
    **모든 예외상황에서 롤백하기 :  @Transactional(rollbackFor = {Exception.class})

 

 

 

 

활용 예제 : 두 테이블에 동시에 insert하기

  1. Service단 메소드에 @Transactional을 붙이고 rollbackFor 조건으로 Exception.class를 붙인다.
  2. 롤백이 되길 원하는 조건에서 throw new Exception("전달메세지"); 를 실행시킨다.
  3. 발생한 예외에 대해서 Service단은 thorow 시키고 Controller단에서 try/catch한다.
  4. Contoller에서는 Service측 메소드에서 예외가 발생했다면 실패, 발생하지 않았다면 성공임을 기반으로 코드를 작성한다.
//Service단
@Transactional(rollbackFor = Exception.class)
@Override
public void insertTest(ADto aDto, BDto bDto) throws Exception {	
    int result = 0;
    //첫번째 insert처리
    result = testMapper.insertTest(aDto);
    if(aResult==0) { throw new Exception("aDto를 insert하는데 실패함"); }

    //두번째 insert처리
    result = testMapper.insertTest(bDto);
    if(bResult<=0) { throw new Exception("bDto를 insert하는데 실패함"); }
}

//Controller단
@PostMapping(value="insert/test")
public Map<String, Object> insertTest(ADto aDto, BDto bDto) {
    Map<String, Object> responseData = new HashMap<>();

    try {
        testService.insertTest(aDto, bDto);
    } catch (Exception e) {
        responseData.put("result", "failure");
        responseData.put("detail", e.getMessage());
        return responseData;			
    }   
    responseData.put("result", "success");
    return responseData;
}

 

 

 

 

 

 

 

 

댓글