CS/DB

스프링 트랜잭션과 트랜잭션 전파

Carnival7 2021. 12. 28. 13:50

스프링 트랜잭션

Isolation

SQL 의 Isolation level 과 동일하게 동작 (SQL 격리수준 속성에 대한 자세한 내용은 이곳을 참고)

  • READ_UNCOMMITED : commit 되지 않은 데이터를 읽는다

  • READ_COMMITED : commit 된 데이터만 읽는다

  • REPEATABLE_READ : 자신의 트랜잭션이 생성되기 이전의 트랜잭션(낮은 번호의 트랜잭션)의 커밋된 데이터만 읽는다

  • SERIALIZABLE : LOCK 을 걸고 사용

  • DEFAULT : 사용하는 DB 기본 설정을 따른다. (Oracle 은 READ_COMMITED, Mysql InnoDB 는 REPEATABLE_READ 가 Default)

스프링 트랜잭션 전파 타입

Propagation

트랜잭션이 시작하거나 참여하는 방법에 관한 설정  
트랜잭션의 경계에서 트랜잭션이 어떻게 동작할 것인가
public class ServiceA {

    private ServiceB serviceB;

    ...

    @Transactional
    public void a() {
        service.b();
    }
}

public class ServiceB {
    @Transactional
    public void b() {...}
}

위의 코드에서처럼 트랜잭션이 처리되는 과정안에서 또 다른 트랜잭션이 처리되는 경우가 있다.

부모 트랜잭션이 있냐 없냐에 따라 타입별로 트랜잭션의 경계를 설정할 수 있다.

@Transactional을 클래스 또는 메서드 레벨에 명시하면 해당 메서드 호출시 지정된 트랜잭션이 작동하게 된다. 해당 클래스의 Bean이 다른 클래스의 Bean에서 호출될 때만 @Transactional을 인지하고 작동하게 된다.
(같은 Bean 내에서 @Transactional이 명시된 다른 메서드를 호출해도 작동하지 않는다.)

스프링 프레임워크는 내부적으로 AOP를 통해서 해당 애노테이션을 인지하여 프록시를 생성해서 트랜잭션을 자동 관리한다.

전파 타입 7가지 요약

  • REQUIRE : 부모 트랜잭션(자신을 호출한 메소드의 Transaction)이 존재한다면 그에 포함되어 동작. 부모 트랜잭션이 존재하지 않을 경우 새로 트랜잭션 생성(default 속성)

  • SUPPORTS : 부모 트랜잭션이 존재하면 그에 포함되어 동작. 부모 트랜잭션이 없다면 트랜잭션 없이 동작.

  • MANDATORY : 부모 트랜잭션이 존재하면 그에 포함되어 동작. 부모 트랜잭션이 없다면 예외 발생시킨다.

  • REQUIRES_NEW : 부모 트랜잭션 존재 여부와 상관없이 트랜잭션을 새로 생성

  • NOT_SUPPORTED : 트랜잭션을 사용하지 않는다. 부모 트랜잭션이 존재하면 보류시킨다.

  • NEVER : 트랜잭션을 사용하지 않도록 강제한다. 부모 트랜잭션이 존재할 경우 예외를 발생시킨다.

  • NESTED : 부모 트랜잭션이 존재하면 부모 트랜잭션 안에 트랜잭션을 만든다. 부모트랜잭션의 커밋과 롤백에 영향을 받지만 자신의 커밋과 롤백은 부모 트랜잭션에 영향을 주지 않는다.

전파 타입 7가지 상세

Propagation.REQUIRED

@Transactional(propagation = Propagation.REQUIRED)
public void doSomething() { ... }
  • 특정 메서드의 트랜잭션이 Propagation.REQUIRED로 설정된 경우 동작방식

  • 기본적으로 해당 메서드를 호출한 곳에서 별도의 트랜잭션이 설정되어있지 않았다면, 트랜잭션을 새로시작! (새로운 연결을 생성하고 실행)

  • 만약 호출한 곳에서 이미 트랜잭션이 설정되어있다면, 기존의 트랜잭션 내에서 로직 실행 (동일한 연결 안에서 실행)

  • 예외가 발생하면 롤백이 되고, 호출한 곳에도 롤백이 전파된다.

  • 기본값이므로 생략 가능

  • 만약 해당 메서드가 호출한 곳과 별도의 스레드라면?

  • 답은 전파레벨과 상관없이 무조건 별도의 트랜잭션을 생성하여 해당 메서드를 실행

  • 스프링은 내부적으로 트랜잭션의 정보를 ThreadLocal 변수에 저장하기 때문에 다른 스레드로 트랜잭션이 전파되지 않는다!!

Propagation.REQUIRES_NEW

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething() { ... }
  • Propagation.REQUIRES_NEW의 경우
  • 매번 새로운 트랜잭션을 시작한다. (새로운 연결(DB 커넥션)을 생성하고 실행한다.)
  • 만약 호출한 곳에서 이미 트랜잭션이 설정되어있다면(기존의 연결이 존재한다면), 기존의 트랜잭션은 메서드가 종료할 때까지 잠시 대기 상태로 두고 자신의 트랜잭션을 실행한다.
  • 새로운 트랜잭션 안에서 예외가 발생해도, 호출한 곳에는 롤백이 전파되지 않는다.
  • 즉 두개의 트랜잭션은 완전히 독립적인 별개의 단위로 작동한다.

Propagation.NESTED

@Transactional(propagation = Propagation.NESTED)
public void doSomething() { ... }
  • Propagation.NESTED의 경우
  • 기본적으로 앞서 설명한 Propagation.REQUIRED와 동일하게 작동
  • 중요한 차이점은 SAVEPOINT를 지정한 시점까지 부분 롤백이 가능하다는 것! (아직 커밋되지 않은 상태에서 SAVEPOINT를 지정할 수 있고 해당 포인트까지 롤백이 가능한 DB의 기능)
  • 유의할 점은 데이터베이스가 SAVEPOINT 기능을 지원해야한다. (대표적으로 ORACLE)
  • 이미 진행중인 트랜잭션이 있다면 중첩 트랜잭션을 시작
  • 해당 메소드가 부모 트랜잭션에서 진행될 경우 별개로 커밋되거나 롤백될 수 있다.
  • 둘러싼 부모 트랜잭션이 없을 경우 Propagation.REQUIRED와 동일하게 동작

Propagation.MANDATORY

@Transactional(propagation = Propagation.MANDATORY)
public void doSomeThing() {...}
  • 부모 트랜잭션 내에서 실행되며, 부모 트랜잭션이 없을 경우 Exception 발생

Propagation.SUPPORT

@Transactional(propagation = Propagation.SUPPORT)
public void doSomeThing() {...}
  • 부모 트랜잭션이 존재하면 부모 트랜잭션으로 동작하고, 없을 경우 non-transactional하게 동작

Propagation.NOT_SUPPORT

@Transactional(propagation = Propagation.NOT_SUPPORT)
public void doSomeThing() {...}
  • non-transactinal로 실행되며, 부모 트랜잭션이 존재하면 부모 트랜잭션을 일시정지하고 수행한다.

Propagation.NEVER

@Transactional(propagation = Propagation.NEVER)
public void doSomeThing() {...}
  • non-transactinal로 실행되며, 부모 트랜잭션이 존재하면 Exception이 발생

추가

  • no-rollback-for : 롤백하지 않을 익셉션 타입
  • rollback-for : 롤백할 익셉션 타입

참고

  1. https://velog.io/@syleemk/%EB%A9%B4%EC%A0%91-%EB%8C%80%EB%B9%84-%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C#%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%EC%9D%B4-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80