본문 바로가기

Java/Spring

트랜잭션 전파(Transaction propagation)에 대한 이해

이 문서의 내용

    더보기

    스프링에서 제공하는 @Transactional의 가장 큰 장점은 여러 개의 트랜잭션을 묶어서 하나의 트랜잭션 경계를 구성 할 수 있다는 점입니다.

    이때 기존의 트랜잭션에 이어서 추가적인 트랜잭션을 열게 되는 경우가 있습니다.

    public class OuterTransaction
    {
    	protected InnerTranscation inner;
        
    	@Transactional
    	public void outer()
    	{
    		inner.inner();
    	}
    }
    
    public class InnerTranscation
    {
    	@Transactional
    	public void inner() { }
    }

    위 코드에서는 outer()에서 트랜잭션이 시작되고, 이어서 inner()에서 추가적인 트랜잭션이 시작됩니다.

    이처럼 이미 트랜잭션이 진행 중인 상황에서 추가 트랜잭션을 어떻게 처리할 것인지를 결정하는 것이 트랜잭션 전파(Transaction propagation)입니다.

    물리 트랜잭션과 논리 트랜잭션

    트랜잭션은 기본적으로 데이터베이스에 의해서 제공되므로 프로그램은 트랜잭션을 사용하기 위해 Connection 객체에 의존합니다.

    결과적으로 프로그램에서 하나의 트랜잭션을 사용하게 되면 하나의 Connection 객체를 사용하는 것과 같습니다. 이를 물리 트랜잭션이라고 부릅니다.

    스프링에서는 Propagation에 의해서 하나의 Connection에서 두 개 이상의 트랜잭션이 사용 될 수 있습니다.

    public class OuterTransaction
    {
    	protected InnerTranscation inner;
        
    	@Transactional
    	public void outer()
    	{
    		inner.inner();
    	}
    }
    
    public class InnerTranscation
    {
    	@Transactional
    	public void inner() { }
    }
    코드 비고
    Line 8 inner.inner() @Transactional이 등록된 메소드에서 호출하는 메소드 역시 @Transactional을 사용합니다.
    동일한 Connection을 사용하고 있음을 유추 할 수 있습니다.

    이 과정에서 실제 데이터베이스에 의한 물리 트랜잭션과 서로 다른 경계가 발생하는데 이를 논리 트랜잭션이라고 정의합니다.

    예시의 경우 두 개의 논리 트랜잭션이 존재하지만 실제로는 1개의 물리 트랜잭션만 존재합니다.

    더보기

    정리하면 물리 트랜잭션은 실제 데이터베이스에서 적용되는 개념으로 Connection을 통해서 커밋과 롤백이 발생하는 행위를 의미합니다.

    반면 논리 트랜잭션은 스프링에서 트랜잭션 매니저를 통해서 관리되는 트랜잭션 영역이자 단위입니다.

    이미 Connection에서 트랜잭션이 실행 중인데 또 다른 트랜잭션이 실행되도록 하려면 상당히 복잡한 상황이 발생합니다.

    스프링에서는 논리 트랜잭션 개념과 2가지 규칙을 통해 이를 단순화합니다.

    • 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션도 커밋됩니다.
    • 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션도 롤백됩니다.

    스프링의 트랜잭션 전파 속성

    스프링의 트랜잭션 전파 속성은 어노테이션의 propagration 속성으로 지정합니다. 이때 디폴트는 REQUIRED입니다.

    @Transactional(propagation = Propagation.REQUIRED)

    스프링에서는 7개의 전파 속성을 제공합니다.

    전파 속성 비고
    REQUIRED 메소드가 실행되기 위해 트랜잭션이 필요합니다.
    진행중인 다른 트랜잭션이 있으면 해당 트랜잭션을 사용합니다. 트랜잭션이 없으면 새로 생성합니다.
    REQUIRES_NEW 항상 새로운 트랜잭션을 시작합니다.
    진행 중인 트랜잭션은 일시 중단하고 새로운 트랜잭션이 종료되면 이어서 실행합니다.
    MANDATORY REQUIRED와 같지만 진행중인 트랜잭션이 존재하지 않으면 Exception이 발생합니다.
    SUPPORTS 메소드가 트랜잭션을 필요로하지 않습니다.
    진행중인 다른 트랜잭션이 있으면 해당 트랜잭션을 사용합니다. 트랜잭션이 없어도 정상 동작합니다.
    NOT_SUPPORTED 메소드가 트랜잭션을 필요로하지 않습니다.
    SUPPORTS와 달리 진행중인 트랜잭션은 일시 중단하고 이 메소드가 종료되면 이어서 실행합니다.
    NEVER 메소드가 트랜잭션을 필요로하지 않습니다.
    진행중인 트랜잭션이 존재하면 Exception이 발생합니다.
    NESTED 진행중인 트랜잭션이 존재하면 기존 트랜잭션에 중첩된 트랜잭션에서 메소드를 실행합니다.
    진행중인 트랜잭션이 존재하지 않으면 REQUIRED와 동일합니다.

    REQUIREDREQUIRES_NEW를 바탕으로 스프링의 전파 속성이 동작하는 원리를 이해할 수 있습니다.

    두 가지 방식만 이해하면 나머지는 표에서 표시된 내용을 기반으로 응용 할 수 있습니다.

    트랜잭션 전파: REQUIRED

    REQUIRED는 스프링의 디폴트 전파 속성입니다.

    기본적인 개념은 2개 이상의 논리 트랜잭션을 하나로 묶어 1개의 물리 트랜잭션 위에서 동작하는 것입니다.

    논리 트랜잭션이 하나로 묶여 있기 때문에 위에서 소개한 2가지 규칙이 적용됩니다.

    • 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션도 커밋됩니다.
    • 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션도 롤백됩니다.

    트랜잭션 전파: REQUIRES_NEW

    항상 새로운 트랜잭션을 시작합니다.

    이는 2개 이상의 트랜잭션이 필요로 할 때 각각의 물리 트랜잭션 위에서 동작하는 것을 의미합니다.

    이 경우에는 2개의 물리 트랜잭션이 생성되므로 각기 다른 Connection을 사용합니다.

    더보기

    외부 트랜잭션에 연결된 리소스는 해당 트랜잭션에 바인딩된 상태로 유지(일시 중지)됩니다. 내부 트랜잭션은 새로운 Connection과 같은 자체 리소스를 획득합니다. 

    이로 인해 Connection 풀이 고갈되거나 교착 상태가 발생하는 원인이 될 수 있습니다.

    REQUIRES_NEW는 독립된 2개의 물리 트랜잭션으로 동작합니다.

    따라서 이론적으로는 내부 트랜잭션에서 롤백이 발생하더라도 외부 트랜잭션에 영향을 주지 않습니다.

    정리 및 복습

    • 스프링에서 제공하는 @Transactional은 여러 개의 트랜잭션을 묶어서 하나의 트랜잭션 경계를 구성합니다.
    • 물리 트랜잭션은 데이터베이스에 연결된 Connection 객체에서 실행됩니다.
    • 스프링에서는 물리 트랜잭션과 구분된 논리 트랜잭션을 도입하고 서로 다른 경계에서 동작하게 합니다.
    • 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션도 커밋됩니다.
    • 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션도 롤백됩니다.
    • 전파 속성 REQUIRES_NEW는 독립된 2개의 물리 트랜잭션으로 동작합니다. 이는 Connection 객체도 2개 생성됨을 의미합니다.
    • 이로 인해 Connection 풀이 고갈되거나 교착 상태가 발생하는 원인이 될 수 있어 사용에 주의해야 합니다.