-
Spring Doc - Core - TransactionDocs/Spring 2021. 5. 3. 18:42
Spring framework Document 읽기
- 현재 담당하는 서비스가 Spring5를 사용하진 않지만 5의 문서를 읽으면서 추가된 내용이 아니라 Spring framework 자체의 내용에 집중해볼 예정입니다.
Docs : https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/data-access.html#spring-data-tier
Repo : https://github.com/spring-projects/spring-framework
Github repository에서 "T"를 누르면 파일 검색이 가능합니다.
Transaction Management
- Transaction 지원은 스프링을 사용함에 있어 가장 강력한 이유중에 하나입니다.
- Spring은 transaction 관리에 일관된 추상화 계층을 제공하고 그로인해서 아래와 같은 이점을 취할 수 있습니다.
- JTA(Java Transaction API), JDBC, Hibernate, JPA등 다양한 transaction API에 대해 일관된 모델을 사용할 수 있습니다.
- 선언적(declarative) 트랜잭션 관리방법 제공합니다.
Declarative는 programmatic한 방식과 반대되는 개념으로 어노테이션, xml 등으로 명시하여 동작하는 방식입니다
- JTA와 같이 복잡한 트랜잭션 API에 비해 간단하게 programmatic 한 방식으로 트랜잭션을 관리 할 수 있는 API가 있습니다.
- Spring data access 추상화 계층과 완벽히 통합가능합니다.
Advantages of the Spring Framework’s Transaction Support Model
- 전통적으로 자바 EE 개발자는 트랜잭션 관리에 global 혹은 local 두가지 선택을 할 수 있었습니다.
Global Transaction
- Global transaction은 db, message queue등 여러 transactional resource에 대한 트랜잭션 관리를 할 수 있게 해줍니다.
- 이전 시대의 Global transaction은 어플리케이션 서버 가 제공하는 트랜잭션 매니저를 JTA를 통해서 사용하는 형태로 동작했으며, JTA의
UserTransaction
은 JNDI(Java Naming and Directory Interface)가 필요함으로 global transaction을 사용하기 위해선 어플리케이션 서버와, JNDI가 필요했습니다.
Local Transaction
- Local transaction은 JDBC connection과 같은 단일 리소스에 대한 트랜잭션 관리를 해줍니다.
- Local transaction은 사용하기 쉽지만 여러 transactional resource에 대한 작업을 할 수 없다는 단점이 있습니다.
Spring Framework’s Consistent Programming Model
- Spring은 global과 local 트랜잭션에 대한 단점을 해결하여 환경과 관계없이 일관성 있는 모델을 사용합니다.
- Global 트랜잭션을 사용하는 경우에도 더이상 어플리케이션 서버가 필요하지 않으며,
선언적인 방식으로 트랜잭션을 관리한다면 코드도 바꿀 필요없이 bean(TransactionManager)만 변경해주면 됩니다.
Understanding the Spring Framework Transaction Abstraction
스프링 트랜잭션의 핵심은 트랜잭션 전략(transaction strategy)에 대한 개념입니다.
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
PlatformTransactionManager
는 트랜잭션 관리를 위한 SPI(service provider interface)입니다.SPI는 같은 기능을 여러 벤더에서 제공할 수 있도록 만든 특별한 인터페이스(API)입니다.
e.g. XML 파서의 경우 인터페이스만 제공하고, 실제 XML 파서는 여러 벤더사에서 구현- 선언적으로 트랜잭션 관리를 하더라도 JDBC, Hibernate등 각 환경에 맞는
PlatformTransactionManager
의 정의는 반드시 필요합니다.<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
JtaTransactionManager
를 사용하는 경우에는 컨테이너의 global transaction management를 사용함으로DataSource
같은 리소스 정보가 필요없습니다
- 선언적으로 트랜잭션 관리를 하더라도 JDBC, Hibernate등 각 환경에 맞는
트랜잭션의 실패는 대부분 코드상에서 복구 할 수 없습니다, 따라서 트랜잭션 과정에서 발생하는
TransactionException
은RuntimeException
으로 제공됩니다.getTransaction
메서드는TransactionDefinition
를 받아서TransactionStatus
을 반환합니다.TransactionStatus
는 트랜잭션이 새로운 것인지, 기존의 트랜잭션인지를 나타냅니다.public interface TransactionStatus extends SavepointManager { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted(); }
TransactionDefinition
specPropagation
- 일반적으로 트랜잭션 내에서 실행된 코드는 해당 트랜잭션에만 영향을 받지만,
트랜잭션 컨텍스트가 이미 생성된 상태(트랜잭션 내부)에서 수행되는 트랜잭션의 행동은 명시적으로 설정할 수 있습니다.e.g. 기존 트랜잭션 컨텍스트내에서 실행되는것이 일반적이지만, 기존 트랜잭션을 잠시 중지하고 새로운 트랜잭션을 만들 수 있습니다.
- 일반적으로 트랜잭션 내에서 실행된 코드는 해당 트랜잭션에만 영향을 받지만,
Isolation
- 트랜잭션이 다른 트랜잭션과 얼마나 분리되어있는지를 나타냅니다.
e.g. 현재 트랜잭션이 다른 트랜잭션의 uncommitted write 정보를 볼 수 있는가?
- 트랜잭션이 다른 트랜잭션과 얼마나 분리되어있는지를 나타냅니다.
Timeout
- 자동 rollback이 이뤄지는 시간을 나타냅니다.
Read-only status
- 데이터 변경이 없는 경우 read-only 트랜잭션을 사용할 수 있습니다.
- Hibernate 환경의 최적화 등에 사용할 수 있습니다.
Synchronizing Resources with Transactions
- 여러 트랜잭션 매니저를 만들고 관련된 트랜잭션 매니저간의 동기화 방법과,
PlatformTransactionManager
을 통해서 동기화가 트리거되는 방법을 알아봅니다
High-level Synchronization
- 이 방식은 resource 생성, 사용, 제거, 트랜잭션 동기화 등이 모두 내부적으로 수행됨으로 사용자는 로직에 집중할 수 있습니다.
JdbcTemplate
을 사용하여 JDBC에 접근하거나 native ORM API를 사용하는것이 이 방식에 속합니다.
Low-level Synchronization
- 이 방식은 high-level 동기화에서 제공하는 기능들을 직접 구현할 때 사용될 수 있습니다.
DataSourceUtils
(JDBC),EntityManagerFactoryUtils
(JPA),SessionFactoryUtils
(Hibernate) 등이 이 방식에 속합니다.e.g. JDBC를 직접 사용할때
getConnection
으로 커넥션을 열고 닫고 하는 동작
TransactionAwareDataSourceProxy
DataSource
에 대한 proxy 객체로 가장 low-level에 존재하는 클래스 입니다.- 대부분의 경우 직접 사용해서는 안되는 클래스입니다.
Datasource
를 programmatic하게 런타임에 set해주는것과 같은 방법으로TransactionAwareDataSourceProxy
를 건드리게되면
해당 리소스는 Spring이 관리하는 트랜잭션에 참여하지 못하게됩니다.
Declarative transaction management
대부분의 사람들이 트랜잭션 관리에 사용하는 방식입니다,
선언적 트랜잭션 관리는 코드에 미치는 영향이 적어서 컨테이너와의 커플링을 낮추는데 도움을 줍니다.AOP를 통해서 선언적 트랜잭션 관리가 동작하게 되지만 Spirng 내부적으로 동작함으로 AOP 개념이 없어도 트랜잭션을 사용할 수 있습니다.
특징
JTA, JDBC, JPA등 환경에 구분없이 동작합니다.
어떤 클래스라도 대상이 될 수 있습니다.
Rollback 규칙을 제공합니다.
e.g. MyApplicationException이 발생하면 자동 롤백
기본 동작은 unchecked exception인 경우 rollback 입니다.Remote call에 대한 propagation을 제공하지 않습니다
AOP를 활용해서 트랜잭션 과정을 커스터마이즈 할 수 있습니다.
e.g. 롤백되는 경우 로깅
선언적 트랜잭션 관리 이해하기
@EnableTransactionManagement
을 달고@Transactional
을 추가한 코드가 어떻게 동작하는지 알아봅니다.- 선언적 트랜잭션 관리의 가장 중요한 컨셉은 AOP 프록시를 통해서 동작한다는것 입니다.
- Transaction Advisor에서 트랜잭션과 관련된 AOP가 수행됩니다. Custom Advisor 와의 수행 순서는 설정에 따라 달라질 수 있습니다.
Transaction을 programmatic하게 관리한다고 가정하고 간단한 흐름을 생각해보면
1.autoCommit을 끄고, 2.@Transactional이 선언된 로직을 수행시키고, 3.커밋 혹은 롤백 을 수행하게 될텐데
Advisor에서 Proxy 객체를 통해서 위의 과정을 수행한다고 생각하면 됩니다.
- Transaction Advisor에서 트랜잭션과 관련된 AOP가 수행됩니다. Custom Advisor 와의 수행 순서는 설정에 따라 달라질 수 있습니다.
@Transactional 사용하기
Configuration 클래스에
@EnableTransactionManagement
를 추가 함으로써 annotation-based의 트랜잭션을 사용할 수 있습니다.@Transactional
은 클래스에도 설정할 수 있지만, 이 경우 부모 클래스의 메소드 호출시에는 동작하지 않으며 추가하기 위해서는 자식 클래스에서 재선언 해 줘야 합니다.- 명시하지 않은 부모 클래스 메서드의 호출이 동작하지 않는건
ProxiedSubInstance.super().method()
와 같은 식으로 동작해서this.method()
와 비슷한 이유 일 것으로 추측되네요Spring팀에서는 인터페이스가 아닌 실제 클래스에만
@Transactional
을 사용하길 권장합니다. 인터페이스에 해당 어노테이션을 붙였을때 transaction 방식이 interface-based가 아닌 class-based면 동작하기 않기 때문
- 명시하지 않은 부모 클래스 메서드의 호출이 동작하지 않는건
@Transactional
은pulbic
메서드에만 동작하며 당연하게도 대상이 bean이여야 합니다.default 설정은 아래와 같습니다
- Propagation :
PROPAGATION_REQUIRED
- Isolation level :
ISOLATION_DEFAULT
- Transaction : read-write
- Timeout : transaction 시스템에 따르거나 없음
- Rollback : 모든
RuntimeException
, checked exception 발생에 대해선 롤백하지 않음
- Propagation :
여러 트랜잭션 매니저 설정 및 커스텀 어노테이션
PlatformTransactionManager
의 구현체를 transactionManager 구현체로 bean에 등록하며 다른 bean들과 마찬가지로 이름(qualifier)를 줄 수 있으며 이를 통해 하나의 어플리케이션에서 여러 트랜잭션 매니저를 등록할 수 있습니다.@Transactional("트랜잭션 매니저 이름")
으로 사용할 매니저를 지정할 수 있으며 커스텀 어노테이션을 통해서 더 편하게 사용할 수 있습니다@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에서의 Transaction Propagation
PROPAGATION_REQUIRED
Default 설정입니다.
트랜잭션이 없는경우 현재 scope의 트랜잭션을 만들고, 트랜잭션이 존재하는 경우 해당 트랜잭션에 참여하며 하나의 물리적인 트랜잭션 내에서 관리됩니다.
- 물리적으로는 하나의 트랜잭션에서 동작하지만 논리적으로는 호출되는 트랜잭션 메서드마다의 스코프를 가지게 됩니다. 일반적인 경우엔 내부 트랜잭션에서 rollback(rollback-only)이 표시되면 외부 트랜잭션도 rollback이 수행되지만,
UnexpectedRollbackException
을 외부 트랜잭션에서 잡음으로써 내부 트랜잭션의 rollback 여부와 관계없이 외부 transaction을 commit 할 수 있습니다.
- 물리적으로는 하나의 트랜잭션에서 동작하지만 논리적으로는 호출되는 트랜잭션 메서드마다의 스코프를 가지게 됩니다. 일반적인 경우엔 내부 트랜잭션에서 rollback(rollback-only)이 표시되면 외부 트랜잭션도 rollback이 수행되지만,
포함되는 transaction은 자신의 isolation level이나 timeout, read-only값이 무시됩니다, 이를 무시하지 않기위해서는 transaction manager에
validateExistingTransactions
플래그를 true로 설정하면 됩니다.
PROPAGATION_REQUIRED_NEW
PROPAGATION_REQUIRED
과 다르게 매번 물리적으로 독립된 새로운 트랜잭션이 생성되고, 외부 트랜잭션은 내부 트랜잭션이 완료될때까지 대기 상태에 있게됩니다.
PROPAGATION_NESTED
- 하나의 물리적인 트랜잭션내에서 rollback을 위한 여러
savepoint
를 갖는 형태로 동작합니다. - 내부 트랜잭션에서 rollback이 일어나서 작업의 일부분이 rollback되더라도 외부 트랜잭션은 계속 트랜잭션 작업을 수행 할 수 있습니다.
- 이 설정은 일반적으로 JDBC의 savepoint에 맵핑됨으로 JDBC transaction 에서만 동작합니다.
- 하나의 물리적인 트랜잭션내에서 rollback을 위한 여러
요약
종류 설명 REQUIRED(Default) 부모 트랜잭션 내에서 실행, 없다면 새로운 트랜잭션 실행 REQUIRED_NEW 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성 SUPPORT 부모 트랜잭션 내에서 실행하고, 없다면 nontransactionally 하게 수행 MANDATORY 부모 트랜잭션 내에서 실행되고, 없다면 예외가 발생 NOT_SUPPORT Nontrasactionally 하게 수행되고, 부모 트랜잭션이 존재하면 일시정지 NEVER Nontrasactionally 하게 수행되고, 부모가 존재하면 예외발생 NESTED 부모트랜잭션이 있으면 별도로 커밋이나 롤백이 될 수 있음, 없다면 REQUIRED와 동일하게 수행 (=그냥 새로 트랜잭션 실행?)
Advice와 @Transactional
@Transactional
의 기본 order는Ordered.LOWEST_PRECEDENCE
(=Integer.MAX_VALUE
)이고 aspect의 기본 order도 동일함으로 order에 따른 결과를 예측하기 어렵습니다. 따라서 advice와@Transactional
을 함께 사용하기 위해선 order를 명시해주는것이 좋습니다.@Transactional
보다 높은 우선순위 를 명시한 aspect에서 수행 시간을 측정하는 advice의 동작 예- Transaction advice(
@Transactional
)와 높은 우선순위의 aspect에 포함된 advice 둘 모두의 대상이 되는 메서드 실행 - 우선 순위가 높은 aspect의 advice 시작 (수행 시간 측정 시작)
- Transactional advice 실행 (transaction 시작)
- Advised object의 메서드 실행
- Transactional advice 종료 (transaction 종료)
- 우선 순위가 높은 aspect의 advice 종료 (수행 시간 측정 종료)
- Transaction advice(
Programmatic Transaction Management
Spring에서는 programmatic하게 트랜잭션을 관리하기 위한 방법으로
TransactionTemplate
,PlatformTransactionManager
두가지를 지원하고 첫번째 방식을 추천합니다.Programmatic한 트랜잭션을 관리는 business로직에 transaction 관리 코드가 섞여 들어가게 됨으로 application에 transactional operation이 매우 적은 경우에만 권징됩니다.
TransactionTemplate 사용하기
Spring의 다른 template과 마찬가지로 application에 독립적이기 위해 callback 방식으로 동작합니다.
Spring transaction에 커플링이 생기지만, 트랜잭션 관리는 대부분 트랜잭션 프레임워크에 커플링이 생길 수 밖에 없습니다.
TransactionTemplate은 thread-safe 하지만 propagation, timeout 등 설정은 인스턴스별로 동일함으로 주의하여야 합니다.
public class SimpleService implements Service { // single TransactionTemplate shared amongst all methods in this instance private final TransactionTemplate transactionTemplate; // use constructor-injection to supply the PlatformTransactionManager public SimpleService(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); this.transactionTemplate.setTimeout(30); // 30 seconds } public Object someServiceMethod() { return transactionTemplate.execute(new TransactionCallback() { // the code in this method executes in a transactional context public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } }); } }
Return이 없다면
TransactionCallbackWithoutResult
를 사용하면 됩니다.
PlatformTransactionManager 사용하기
PlatformTransactionManager의 구현체를 bean에 등록하고
TransactionDefinition
와TransactionStatus
를 사용하 programmatic 하게 트랜잭션을 관리 할 수 있습니다.DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // explicitly setting the transaction name is something that can be done only programmatically def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { // execute your business logic here } catch (MyException ex) { txManager.rollback(status); throw ex; } txManager.commit(status);
'Docs > Spring' 카테고리의 다른 글
Spring Doc - Core - Web MVC (0) 2021.05.03 Spring Doc - Core -IOC Container (0) 2021.05.03 Spring Doc - Core - AOP (0) 2021.05.03 - 현재 담당하는 서비스가 Spring5를 사용하진 않지만 5의 문서를 읽으면서 추가된 내용이 아니라 Spring framework 자체의 내용에 집중해볼 예정입니다.