Spring Transaction의 이해
안녕하세요. yeTi입니다.
오늘은 Spring Framework에서 Transaction을 관리하는 방법에 대해 알아보겠습니다.
Spring Framework Reference와 Spring 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
orPROPAGATION_REQUIRES_NEW
옵션에서 사용할 수 있다. - Read-only : 하이버네이트를 사용할 때 주로 사용하는 옵션인데, 읽기 전용 트랜젝션인지를 확인한다.
이와같이 TransactionDefinition
의 설정 값들은 일반적인 트랜젝션의 컨셉으로 제공하고 있습니다.
선언적 트랜젝션 관리
선언적 트랜젝션 관리
는 스프링 AOP
라는 기술이 있으므로해서 제공가능합니다. 따라서 AOP
의 목적과 같이 트랜젝션을 위한 코드는 비즈니스 코드와 분리되어 관리되고 이는 개발자가 트랜젝션에 대해 크게 신경써도 되지 않도록 해줍니다.
위 그림과 같이 Target Method
가 호출되면 Transaction Advisor
가 트랜젝션을 생성합니다. Target Method
의 동작이 끝나면 Transaction Advisor
는 commit
이나 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에서 사용하는 트랜젝션의 구조를 도식화하면 다음과 같습니다.
DB 커넥션의 생성 flow
Transaction Manager bean
을 생성합니다.DB 커넥션
을 생성합니다.DB Pool
을 생성합니다.AOP
를 위한advice
를 맵핑합니다.
요청 flow
Advice
하고 있는point
를 요청합니다.Bean
에 커넥션을 할당합니다.- 트랜젝션을 시작합니다.
Bean
을 수행합니다.- 트랜젝션을 종료합니다.
Bean
으로부터 커넥션을 반환받습니다.
Multi transaction 관리
다수의 트랜젝션
을 관리하는 경우 각각의 데이터소스
를 기반으로 transaction manager
를 각각 생성합니다. 따라서 AOP
가 동작하게 되면 데이터소스
별로 커넥션
을 가져오기 때문에 트랜젝션
또한 별도로 관리됩니다.