Loading...
Spring Framework Reference Documentation 7.0.2의 Example of Declarative Transaction Implementation의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
다음 인터페이스와 그에 수반되는 구현을 고려해 보십시오. 이 예제에서는
Foo와 Bar 클래스를 placeholder로 사용하므로, 특정 도메인 모델에 집중하지 않고
트랜잭션 사용에 집중할 수 있습니다.
이 예제의 목적상,
DefaultFooService 클래스가 각 구현된 메서드의 body에서
UnsupportedOperationException 인스턴스를 던진다는 사실은 좋습니다. 이 동작을 통해
트랜잭션이 생성된 다음 UnsupportedOperationException 인스턴스에 대한 응답으로
롤백되는 것을 볼 수 있습니다. 다음 목록은 FooService 인터페이스를 보여 줍니다:
1// the service interface that we want to make transactional 2 3package x.y.service; 4 5public interface FooService { 6 7 Foo getFoo(String fooName); 8 9 Foo getFoo(String fooName, String barName); 10 11 void insertFoo(Foo foo); 12 13 void updateFoo(Foo foo); 14 15}
1// the service interface that we want to make transactional 2 3package x.y.service 4 5interface FooService { 6 7 fun getFoo(fooName: String): Foo 8 9 fun getFoo(fooName: String, barName: String): Foo 10 11 fun insertFoo(foo: Foo) 12 13 fun updateFoo(foo: Foo) 14}
다음 예제는 앞의 인터페이스의 구현을 보여 줍니다:
1package x.y.service; 2 3public class DefaultFooService implements FooService { 4 5 @Override 6 public Foo getFoo(String fooName) { 7 // ... 8 } 9 10 @Override 11 public Foo getFoo(String fooName, String barName) { 12 // ... 13 } 14 15 @Override 16 public void insertFoo(Foo foo) { 17 // ... 18 } 19 20 @Override 21 public void updateFoo(Foo foo) { 22 // ... 23 } 24}
1package x.y.service 2 3class DefaultFooService : FooService { 4 5 override fun getFoo(fooName: String): Foo { 6 // ... 7 } 8 9 override fun getFoo(fooName: String, barName: String): Foo { 10 // ... 11 } 12 13 override fun insertFoo(foo: Foo) { 14 // ... 15 } 16 17 override fun updateFoo(foo: Foo) { 18 // ... 19 } 20}
FooService 인터페이스의 처음 두 메서드, getFoo(String)과
getFoo(String, String)은 읽기 전용 시맨틱스를 가진 트랜잭션 컨텍스트에서
실행되어야 하고, 다른 메서드인 insertFoo(Foo)와 updateFoo(Foo)는
읽기/쓰기 시맨틱스를 가진 트랜잭션 컨텍스트에서 실행되어야 한다고 가정합니다.
다음 설정은 다음 몇 단락에서 자세히 설명합니다:
1<!-- from the file 'context.xml' --> 2<?xml version="1.0" encoding="UTF-8"?> 3<beans xmlns="http://www.springframework.org/schema/beans" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xmlns:tx="http://www.springframework.org/schema/tx" 7 xsi:schemaLocation=" 8 http://www.springframework.org/schema/beans 9 https://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/tx 11 https://www.springframework.org/schema/tx/spring-tx.xsd 12 http://www.springframework.org/schema/aop 13 https://www.springframework.org/schema/aop/spring-aop.xsd"> 14 15 <!-- this is the service object that we want to make transactional --> 16 <bean id="fooService" class="x.y.service.DefaultFooService"/> 17 18 <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) --> 19 <tx:advice id="txAdvice" transaction-manager="txManager"> 20 <!-- the transactional semantics... --> 21 <tx:attributes> 22 <!-- all methods starting with 'get' are read-only --> 23 <tx:method name="get*" read-only="true"/> 24 <!-- other methods use the default transaction settings (see below) --> 25 <tx:method name="*"/> 26 </tx:attributes> 27 </tx:advice> 28 29 <!-- ensure that the above transactional advice runs for any execution 30 of an operation defined by the FooService interface --> 31 <aop:config> 32 <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/> 33 <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/> 34 </aop:config> 35 36 <!-- don't forget the DataSource --> 37 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 38 <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> 39 <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/> 40 <property name="username" value="scott"/> 41 <property name="password" value="tiger"/> 42 </bean> 43 44 <!-- similarly, don't forget the TransactionManager --> 45 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 46 <property name="dataSource" ref="dataSource"/> 47 </bean> 48 49 <!-- other <bean/> definitions here --> 50 51</beans>
앞의 설정을 살펴보십시오. 이 설정은 서비스 객체,
즉 fooService 빈을 트랜잭션하게 만들고자 한다고 가정합니다.
적용할 트랜잭션 시맨틱스는 <tx:advice/> 정의에 캡슐화되어 있습니다.
<tx:advice/> 정의는 " get으로 시작하는 모든 메서드는 읽기 전용
트랜잭션 컨텍스트에서 실행되고, 다른 모든 메서드는 기본 트랜잭션 시맨틱스로
실행된다"라고 읽을 수 있습니다. <tx:advice/> 태그의 transaction-manager
애트리뷰트는 트랜잭션을 구동할 TransactionManager 빈의 이름으로 설정됩니다
(이 경우 txManager 빈).
트랜잭션 advice(
<tx:advice/>)에서transaction-manager애트리뷰트는 연결하려는TransactionManager의 빈 이름이transactionManager인 경우 생략할 수 있습니다. 연결하려는TransactionManager빈의 이름이 다른 경우, 앞의 예제와 같이 반드시transaction-manager애트리뷰트를 명시적으로 사용해야 합니다.
<aop:config/> 정의는 txAdvice 빈에 의해 정의된 트랜잭션 advice가
프로그램의 적절한 지점에서 실행되도록 보장합니다. 먼저 FooService 인터페이스에
정의된 어떤 오퍼레이션의 실행에도 일치하는 포인트컷
(fooServiceOperation)을 정의합니다.
그런 다음 어드바이저를 사용하여 포인트컷을
txAdvice와 연관시킵니다. 그 결과, fooServiceOperation의 실행 시점에
txAdvice에 의해 정의된 advice가 실행됩니다.
<aop:pointcut/> 엘리먼트 내에 정의된 expression은 AspectJ 포인트컷 expression입니다.
Spring에서 포인트컷 expression에 대한 자세한 내용은
the AOP section을 참조하십시오.
일반적인 요구 사항은 전체 서비스 레이어를 트랜잭션하게 만드는 것입니다. 이를 수행하는 가장 좋은 방법은 포인트컷 expression을 서비스 레이어의 어떤 오퍼레이션에도 일치하도록 변경하는 것입니다. 다음 예제는 그 방법을 보여 줍니다:
1<aop:config> 2 <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/> 3 <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/> 4</aop:config>
앞의 예제에서는 모든 서비스 인터페이스가
x.y.service패키지에 정의되어 있다고 가정합니다. 자세한 내용은 the AOP section을 참조하십시오.
이제 설정을 분석했으므로, 스스로에게 "이 모든 설정이 실제로 무엇을 하는가?"라는 질문을 할 수 있습니다.
앞에서 보여 준 설정은 fooService 빈 정의에서 생성된 객체
주변에 트랜잭션 프록시를 생성하는 데 사용됩니다. 프록시는 트랜잭션 advice로
구성되어 있으므로, 프록시에서 적절한 메서드가 호출될 때, 해당 메서드와 연관된
트랜잭션 설정에 따라 트랜잭션이 시작, 일시 중단, 읽기 전용으로
표시되는 등의 동작을 수행합니다.
앞에서 보여 준 설정을 test drive하는 다음 프로그램을 살펴보십시오:
1public final class Boot { 2 3 public static void main(final String[] args) throws Exception { 4 ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml"); 5 FooService fooService = ctx.getBean(FooService.class); 6 fooService.insertFoo(new Foo()); 7 } 8}
1import org.springframework.beans.factory.getBean 2 3fun main() { 4 val ctx = ClassPathXmlApplicationContext("context.xml") 5 val fooService = ctx.getBean<FooService>("fooService") 6 fooService.insertFoo(Foo()) 7}
앞의 프로그램을 실행한 출력은 다음과 유사해야 합니다 (Log4J 출력과
DefaultFooService 클래스의 insertFoo(..) 메서드에서 던져진
UnsupportedOperationException의 스택 트레이스는 명확성을 위해 생략되었습니다):
1<!-- the Spring container is starting up... --> 2[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors 3 4<!-- the DefaultFooService is actually proxied --> 5[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService] 6 7<!-- ... the insertFoo(..) method is now being invoked on the proxy --> 8[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo 9 10<!-- the transactional advice kicks in here... --> 11[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo] 12[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction 13 14<!-- the insertFoo(..) method from DefaultFooService throws an exception... --> 15[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException 16[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException] 17 18<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) --> 19[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] 20[DataSourceTransactionManager] - Releasing JDBC Connection after transaction 21[DataSourceUtils] - Returning JDBC Connection to DataSource 22 23Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14) 24<!-- AOP infrastructure stack trace elements removed for clarity --> 25at $Proxy0.insertFoo(Unknown Source) 26at Boot.main(Boot.java:11)
리액티브 트랜잭션 관리를 사용하려면 코드가 리액티브 타입을 사용해야 합니다.
Spring Framework는 메서드 반환 타입이 리액티브인지 여부를 판단하기 위해
ReactiveAdapterRegistry를 사용합니다.
다음 목록은 앞에서 사용한 FooService의 수정된 버전을 보여 주지만,
이번에는 코드가 리액티브 타입을 사용합니다:
1// the reactive service interface that we want to make transactional 2 3package x.y.service; 4 5public interface FooService { 6 7 Flux<Foo> getFoo(String fooName); 8 9 Publisher<Foo> getFoo(String fooName, String barName); 10 11 Mono<Void> insertFoo(Foo foo); 12 13 Mono<Void> updateFoo(Foo foo); 14 15}
1// the reactive service interface that we want to make transactional 2 3package x.y.service 4 5interface FooService { 6 7 fun getFoo(fooName: String): Flow<Foo> 8 9 fun getFoo(fooName: String, barName: String): Publisher<Foo> 10 11 fun insertFoo(foo: Foo) : Mono<Void> 12 13 fun updateFoo(foo: Foo) : Mono<Void> 14}
다음 예제는 앞의 인터페이스의 구현을 보여 줍니다:
1package x.y.service; 2 3public class DefaultFooService implements FooService { 4 5 @Override 6 public Flux<Foo> getFoo(String fooName) { 7 // ... 8 } 9 10 @Override 11 public Publisher<Foo> getFoo(String fooName, String barName) { 12 // ... 13 } 14 15 @Override 16 public Mono<Void> insertFoo(Foo foo) { 17 // ... 18 } 19 20 @Override 21 public Mono<Void> updateFoo(Foo foo) { 22 // ... 23 } 24}
1package x.y.service 2 3class DefaultFooService : FooService { 4 5 override fun getFoo(fooName: String): Flow<Foo> { 6 // ... 7 } 8 9 override fun getFoo(fooName: String, barName: String): Publisher<Foo> { 10 // ... 11 } 12 13 override fun insertFoo(foo: Foo): Mono<Void> { 14 // ... 15 } 16 17 override fun updateFoo(foo: Foo): Mono<Void> { 18 // ... 19 } 20}
명령형 트랜잭션 관리와 리액티브 트랜잭션 관리는 트랜잭션 경계와 트랜잭션 애트리뷰트 정의에 대해 동일한 시맨틱스를 공유합니다. 명령형 트랜잭션과 리액티브 트랜잭션의 주요 차이점은 후자의 지연된 특성입니다.
TransactionInterceptor는 트랜잭션을 시작하고
정리하기 위해 반환된 리액티브 타입을 트랜잭션 연산자로 장식합니다.
따라서 트랜잭션 리액티브 메서드를 호출하는 것은 실제 트랜잭션 관리를
리액티브 타입의 처리를 활성화하는 subscription 타입으로 지연시킵니다.
리액티브 트랜잭션 관리의 또 다른 측면은 프로그래밍 모델의 자연스러운 결과인 데이터 이스케이핑과 관련이 있습니다.
명령형 트랜잭션의 메서드 반환 값은 메서드가 성공적으로 종료될 때 트랜잭션 메서드에서 반환되므로, 부분적으로 계산된 결과가 메서드 클로저를 벗어나지 않습니다.
리액티브 트랜잭션 메서드는 computation sequence와 computation을 시작하고 완료할 promise를 함께 나타내는 리액티브 래퍼 타입을 반환합니다.
Publisher는 트랜잭션이 진행 중인 동안 (반드시 완료된 것은 아니지만)
데이터를 emit할 수 있습니다. 따라서 전체 트랜잭션의 성공적인 완료에 의존하는
메서드는 호출 코드에서 완료를 보장하고 결과를 버퍼해야 합니다.
Understanding the Spring Framework’s Declarative Transaction Implementation
Rolling Back a Declarative Transaction