IT/Spring

Spring Transaction의 이해

yeTi 2020. 1. 30. 17:50

안녕하세요. yeTi입니다.
오늘은 Spring Framework에서 Transaction을 관리하는 방법에 대해 알아보겠습니다.

Spring Framework ReferenceSpring Javadoc을 참고해서 작성했습니다.

스프링 프레임워크에 대한 전체적인 개념을 알고 싶으시면 Spring Framework 개념 이해하기를 확인해보세요.

개요

스프링 프레임워크는 트랜젝션 관리에 다음과 같은 장점을 가지고 있습니다.

  • Java Transaction API (JTA), JDBC, Hibernate, and the Java Persistence API (JPA)와 같은 다양한 형태의 트랜젝션 API를 지원합니다.
  • 선언적 트랜젝션 관리를 지원합니다.
  • JTA처럼 복잡하지 않은 프로그래밍 방식의 트랜젝션 관리를 지원합니다.
  • 데이터 접근을 위한 추상화를 제공합니다.

PlatformTransactionManager - 트랜젝션의 시작

스프링에서는 org.springframework.transaction.PlatformTransactionManager 인터페이스를 통하여 트랜젝션 전략을 정의합니다. 서비스 제공자는 해당 인터페이스를 직접 사용하여 트랜젝션을 사용할 수 있고 AOP를 통하여 트랜젝션을 사용할 수 있습니다.

org.springframework.transaction.PlatformTransactionManager는 다음과 같이 세 개의 인터페이스를 제공합니다.

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

위에서 본 바와같이 인터페이스에서 TransactionException을 제공하기 때문에 개발자는 해당 익셉션을 catch하여 후처리를 할 수 있습니다.

getTransaction 함수를 보면
TransactionDefinition을 인자로 받아 TransactionStatus를 반환하는 것을 볼 수 있는데 다음과 같은 기능은 인터페이스로 제공합니다.

  • Propagation : 트랜젝션의 상속을 어떤식으로 처리할 것인가를 정의합니다.
  • Isolation : 다수의 트랜젝션이 동작시 각 트랜젝션의 데이터 격리 수준을 어떤 수준으로 할 것인가는 정의합니다.
  • Timeout : 트랜젝션이 새롭게 시작된 후부터 제공하는 시간이며 PROPAGATION_REQUIRED or PROPAGATION_REQUIRES_NEW 옵션에서 사용할 수 있다.
  • Read-only : 하이버네이트를 사용할 때 주로 사용하는 옵션인데, 읽기 전용 트랜젝션인지를 확인한다.

이와같이 TransactionDefinition의 설정 값들은 일반적인 트랜젝션의 컨셉으로 제공하고 있습니다.

선언적 트랜젝션 관리

선언적 트랜젝션 관리스프링 AOP라는 기술이 있으므로해서 제공가능합니다. 따라서 AOP의 목적과 같이 트랜젝션을 위한 코드는 비즈니스 코드와 분리되어 관리되고 이는 개발자가 트랜젝션에 대해 크게 신경써도 되지 않도록 해줍니다.

위 그림과 같이 Target Method가 호출되면 Transaction Advisor가 트랜젝션을 생성합니다. Target Method의 동작이 끝나면 Transaction Advisorcommit이나 rollback을 수행하고 요청을 끝냅니다.

XML기반 선언적 트랜젝션의 예제

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}
package x.y.service;

public class DefaultFooService implements FooService {

    @Override
    public Foo getFoo(String fooName) {
        // ...
    }

    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public void insertFoo(Foo foo) {
        // ...
    }

    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}
<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/>
            <!-- other methods use the default transaction settings (see below) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- ensure that the above transactional advice runs for any execution
        of an operation defined by the FooService interface -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>

    <!-- don't forget the DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>

    <!-- similarly, don't forget the PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- other <bean/> definitions here -->

</beans>

Annotaion기반 선언적 트랜젝션의 예제

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- enable the configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required --> 

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- (this dependency is defined somewhere else) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- other <bean/> definitions here -->

</beans>

클래스에서는 다음과 같이 @Transactional을 선언합니다.

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName) {
        // ...
    }

    Foo getFoo(String fooName, String barName) {
        // ...
    }

    void insertFoo(Foo foo) {
        // ...
    }

    void updateFoo(Foo foo) {
        // ...
    }
}

다음 예제와 같이 클래서에 선언한 @Transactional을 함수에서 오버라이드하여 사용할 수 있습니다.

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // ...
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // ...
    }
}

다수의 TransactionManager 활용 - Qualifier

public class TransactionalService {

    @Transactional("order")
    public void setSomething(String name) { ... }

    @Transactional("account")
    public void doSomething() { ... }
}
<tx:annotation-driven/>

    <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="order"/>
    </bean>

    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="account"/>
    </bean>

다수의 TransactionManager 활용 - Annotation

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}
public class TransactionalService {

    @OrderTx
    public void setSomething(String name) {
        // ...
    }

    @AccountTx
    public void doSomething() {
        // ...
    }
}

개념도

위의 내용을 기반으로 Spring에서 사용하는 트랜젝션의 구조를 도식화하면 다음과 같습니다.

Spring의 트랜젝션 관리 구조

DB 커넥션의 생성 flow

  1. Transaction Manager bean을 생성합니다.
  2. DB 커넥션을 생성합니다.
  3. DB Pool을 생성합니다.
  4. AOP를 위한 advice를 맵핑합니다.

요청 flow

  1. Advice하고 있는 point를 요청합니다.
  2. Bean에 커넥션을 할당합니다.
  3. 트랜젝션을 시작합니다.
  4. Bean을 수행합니다.
  5. 트랜젝션을 종료합니다.
  6. Bean으로부터 커넥션을 반환받습니다.

Multi transaction 관리

multi transaction 관리

다수의 트랜젝션을 관리하는 경우 각각의 데이터소스를 기반으로 transaction manager를 각각 생성합니다. 따라서 AOP가 동작하게 되면 데이터소스별로 커넥션을 가져오기 때문에 트랜젝션 또한 별도로 관리됩니다.