Spring 트랜잭션
Spring Framework에서 제공하는 트랜잭션 관리 기능을 제공합니다.
(트랜잭션이란 데이터베이스나 다른 영속성 저장소에서 여러 개의 연산을 하나의 논리적 단위로 묶어서 원자성, 일관성, 격리성, 지속성을 보장하는 작업 단위입니다.)
Spring 트랜잭션
- 선언적 트랜잭션 관리
Spring은 `@Transactional` 어노테이션을 사용하여 선언적 트랜잭션 관리를 지원합니다. 이를 통해 개발자는 트랜잭션 관련 코드를 직접 작성하지 않고도 간편하게 트랜잭션을 사용할 수 있습니다. - 트랜잭션 경계 설정
Spring은 트랜잭션 경계를 설정하여 트랜잭션의 시작과 종료를 자동으로 처리합니다. 경계 안에서 메서드가 정상적으로 종료되면 커밋하고, 예외가 발생하면 롤백합니다. 트랜잭션 경계를 설정하는 방법에는 어노테이션, XML 설정, AOP 등 다양한 방식이 있습니다. - 트랜잭션 속성 정의
`@Transactional` 어노테이션을 사용하여 트랜잭션의 동작을 세밀하게 제어할 수 있습니다.
isolation, propagation, readOnly, timeout 등의 속성을 설정하여 트랜잭션의 격리 수준, 전파 동작, 읽기 전용 여부, 제한 시간 등을 지정할 수 있습니다. - 다양한 트랜잭션 매니저 지원
Spring은 다양한 트랜잭션 매니저를 지원합니다. JDBC, JPA, Hibernate, JTA 등 다양한 영속성 기술에 대한 트랜잭션 매니저를 제공하여 트랜잭션 관리를 일관되게 처리할 수 있습니다. - AOP 기반으로 동작
선언적 트랜잭션은 Spring의 AOP (Aspect-Oriented Programming)를 기반으로 동작합니다. AOP는 프록시 객체를 사용하여 트랜잭션 관련 코드를 주입하고, 트랜잭션 경계를 설정합니다.
@Transactional 설정
방법1: XML
Spring 설정 파일에 `transactionManager` 빈을 등록. 일반적으로 DB 트랜잭션을 다루는 `DataSourceTransactionManager`를 사용합니다.
<!-- 트랜잭션 활성화: @Transactional 어노테이션 인식하도록 함 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- 데이터베이스 연결 정보를 설정하여 DataSource를 생성 -->
<bean id="dataSource" class="com.example.DataSource">
<!-- ... -->
</bean>
<!-- DataSourceTransactionManager를 생성하여 트랜잭션 매니저로 등록 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
방법2: Java 기반
`@EnableTransactionManagement` 어노테이션을 설정 파일에 추가하여 Spring의 트랜잭션 관리를 활성화
@Configuration
@EnableTransactionManagement // 트랜잭션 관리 기능 활성화
public class AppConfig {
@Bean
public DataSource dataSource() {
// DB 연결 정보를 설정
// ...
}
@Bean
public PlatformTransactionManager transactionManager() {
// DataSourceTransactionManager를 생성하여 트랜잭션 매니저로 등록
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
}
⭐`트랜잭션` 적용하려면 해당 메서드나 클래스는 빈으로 등록되어 있어야 합니다!!
PlatformTransactionManager 인터페이스는 트랜잭션 처리에 필요한 API를 제공해줍니다. 스프링 프레임워크는 다양한 환경과 제품에 대응하는 PlatformTransactionManager의 구현 클래스를 제공하는데 기본적으로 `DataSourceTransactionManager`를 사용합니다.
DataSourceTransactionManager | JDBC 및 mybatis 등의 JDBC 기반 라이브버리로 DB 접근하는 경우 이용 |
HibernateTransactionManager | Hibernate를 이용해 DB 접근하는 경우 이용 |
JpaTransactionManager | JPA를 이용해 DB 접근하는 경우 이용 |
JtaTransactionManager | JTA에서 트랜잭션을 관리하는 경우 이용 (글로벌 트랜잭션을 이용하는 경우) |
WebLogincJtaTransactionManager | 어플리케이션 서버인 웹로직의 JTA에서 트랜잭션을 관리하는 경우에 이용 |
WebSphereUowTransactionManager | 어플리케이션 서버인 웹스피어의 JTA에서 트랜잭션을 관리하는 경우에 이용 |
@Transactional 사용 및 속성
@Service
public class AccountService {
private final AccountRepository accountRepository;
@Autowired
public AccountService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Transactional
public void transferMoney(String fromAccount, String toAccount, double amount) {
try {
// 출금 계좌에서 잔액 조회
Account from = accountRepository.findByAccountNumber(fromAccount);
double currentBalance = from.getBalance();
if (currentBalance < amount) {
throw new InsufficientBalanceException("Insufficient balance in the account.");
}
// 출금 계좌 돈 감소
from.setBalance(currentBalance - amount);
accountRepository.updateAccount(from);
// 입금 계좌 돈 조회
Account to = accountRepository.findByAccountNumber(toAccount);
double toCurrentBalance = to.getBalance();
// 입금 계좌 돈 증가
to.setBalance(toCurrentBalance + amount);
accountRepository.updateAccount(to);
} catch (Exception e) {
// 예외 발생 시 RollBack
throw new TransferException("Failed to transfer money.", e);
}
}
}
- `@Transactional` 어노테이션은 메서드 수준에 적용되어 해당 메서드가 트랜잭션 경계 내에서 실행됨을 나타냅니다.
- 해당 메서드가 실행 전에 트랜잭션이 시작되고, 메서드 실행 후에 커밋 또는 롤백이 수행됩니다.
- 예외가 발생한 경우(`TransferException`) Rollback됩니다.
클래스 수준에 `@Transactional` 적용
- `@Transactional` 어노테이션은 클래스 수준에도 적용될 수 있습니다. 아래 처럼 `AccountService` 클래스에 적용할 경우 `AccountService` 클래스의 모든 메서드에 트랜잭션 설정이 적용됩니다. 메서드 수준에서 `@Transactional`을 부여하였다면 클래스에 부여한 것보다 우선해서 적용됩니다.
@Service
@Transactional
public class AccountService {
private final AccountRepository accountRepository;
@Autowired
public AccountService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
public void transferMoney(String fromAccount, String toAccount, double amount) {
// 이체 로직
}
public void withdrawMoney(String accountNumber, double amount) {
// 출금 로직
}
public void depositMoney(String accountNumber, double amount) {
// 입금 로직
}
}
예외 발생시 RollBack 여부 지정
Spring Framework의 `@Transactional` 어노테이션은 `RuntimeException`과 그 하위 예외들이 발생하면 RollBack을 수행합니다. `RuntimeException`예외는 실행 시 발생하는 예외로 다음과 같은 예외가 있습니다.
- NullPointerException / ArrayIndexOutOfBoundsException, IllegalArgumentException, ...
즉, 위에서 예시로 들었던 코드의 `TransferException`도 다음과 같이 정의되면 `RuntimeException`의 하위 예외이기 때문에 `TransferException`이 발생하면 트랜잭션은 RollBack 됩니다.
public class TransferException extends RuntimeException {
// ...
}
`RuntimeException`의 하위 예외가 아니어도 예외가 발생시 RollBack을 발생하게 할 수 있고 `RuntimeException`의 하위 예외여도 예외 발생시 Rollback을 발생하지 않게 할 수 있습니다. 다음과 같이 `rollbackFor` 속성과 `noRollbackFor` 속성을 사용하면 됩니다.
@Transactional(rollbackFor = { CustomException1.class }, noRollbackFor = { CustomException2.class })
public void method() {
// ...
}
위 예제 코드에서는, `CustomException1`이 발생하면 RollBack되고, `CustomException2`이 발생하도 RollBack되지 않습니다.
전파 방식: propagation
@Transactional 어노테이션의 `propagation 속성`은 트랜잭션의 전파 방식을 지정하는 데 사용됩니다. 트랜잭션의 전파 방식은 메서드 내에서 이미 실행 중인 트랜잭션이 있는 경우, 새로운 트랜잭션이 시작될 때 어떻게 동작할지를 결정합니다.
propagation 속성
- `Propagation.REQUIRED` (Default)
- 메소드를 수행하는데 트랜잭션이 필요하다는 것을 의미
- 현재 진행중인 트랜잭션이 존재하면 해당 트랜잭션을 사용
- 존재하지 않으면 새로운 트랜잭션을 생성
- `Propagation.REQUIRES_NEW`
- 항상 새로운 트랜잭션을 시작한다.
- 진행 중인 트랜잭션이 있을 경우 기존 트랜잭션을 일시 중지하고 새로운 트랜잭션을 시작
- 새로 시작된 트랜잭션이 종료된 뒤에 기존 트랜잭션이 계속 진행됨.
- `Propagation.SUPPORTS`
- 메소드가 트랜잭션을 필요로 하지 않지만,
- 진행 중인 트랜잭션이 존재하면 해당 트랜잭션에 참여
- 진행 중인 트랜잭션이 존재하지 않으면 트랜잭션이 없이 실행
- `Propagation.NOT_SUPPORTED`
- 메소드가 트랜잭션을 필요로 하지 않는다는 의미
- 진행 중인 트랜잭션이 존재할 경우 해당 트랜잭션을 일시 중지시키고, 트랜잭션 없이 실행시킵니다. 실행이 종료된 후에 정지 시킨 트랜잭션을 계속 진행합니다.
- `Propagation.MANDATORY`
- 메소드를 수행하는데 트랜잭션이 필요하다는 것을 의미
- 진행 중인 트랜잭션이 존재하면 해당 트랜잭션에 참여
- 진행 중인 트랜잭션이 존재하지 않을 경우 예외가 발생
- `Propagation.NEVER`
- 메소드가 트랜잭션을 필요로 하지 않는다는 의미
- 진행 중인 트랜잭션이 존재하면 예외가 발생
- 진행 중인 트랜잭션이 존재하지 않을 경우 트랜잭션 없이 실행
- `Propagation.NESTED`
- 메소드를 수행하는데 트랜잭션이 필요하다는 것을 의미
- 진행 중인 트랜잭션이 존재하면 기존 트랜잭션에 중첩된 트랜잭션에서 메소드를 실행
- 진행 중인 트랜잭션이 존재하지 않으면 새로운 트랜잭션을 생성
- JDBC 3.0 드라이버를 사용할 때만 적용
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method() {
// ...
}
위의 예제에서는 항상 새로운 트랜잭션을 시작하여 method() 메서드를 실행합니다. 즉, 이미 실행 중인 트랜잭션이 있다면 일시 중지시키고 새로운 트랜잭션을 시작합니다.
트랜잭션의 전파 방식은 다른 서비스 메서드를 호출하거나 동일한 클래스 내에서 다른 @Transactional 메서드를 호출할 때 유용하게 활용됩니다. 이를 통해 여러 메서드 간에 트랜잭션의 경계를 조절하고, 원하는 트랜잭션 동작을 구현할 수 있습니다.
격리 수준: isolation
@Transactional 어노테이션의 isolation 속성은 트랜잭션의 격리 수준을 지정하는 데 사용됩니다.
격리 수준은 동시에 DB에 접근할 때 그 접근을 어떻게 제어할지에 대한 설정입니다.
isolation 속성
- `Isolation.DEFAULT`
- 데이터베이스의 기본 격리 수준을 사용합니다.
- 일반적으로 데이터베이스 벤더의 기본값으로 설정됩니다.
- `Isolation.READ_UNCOMMITTED`
- 커밋되지 않은 데이터에 대한 읽기를 허용합니다.
- 다른 트랜잭션에서 수정 중인 데이터를 읽을 수 있으며, 이로 인해 "Dirty Read"와 "Non-Repeatable Read" 문제가 발생할 수 있습니다.
- `Isolation.READ_COMMITTED`
- 커밋된 데이터만 읽을 수 있습니다.
- 다른 트랜잭션에서 수정 중인 데이터는 읽을 수 없지만, 다른 트랜잭션에서 커밋한 데이터는 읽을 수 있습니다. "Dirty Read" 문제는 발생하지 않지만, "Non-Repeatable Read" 문제는 여전히 발생할 수 있습니다.
- `Isolation.REPEATABLE_READ`
- 한 번 읽은 데이터는 트랜잭션 종료까지 동일한 값을 보장합니다.
- 즉, 처음에 읽어 온 데이터와 두 번째 읽어온 데이터가 동일합니다.
- 다른 트랜잭션에서 수정 중인 데이터를 읽지 않으며, "Dirty Read"와 "Non-Repeatable Read" 문제가 발생하지 않습니다. 하지만 "Phantom Read" 문제는 여전히 발생할 수 있습니다.
- `Isolation.SERIALIZABLE`
- 같은 데이터에 대해서 한 개의 트랜잭션만 수행 가능합니다.
- 즉, 동시에 실행되는 트랜잭션이 서로 영향을 미치지 않도록 보장합니다.
- "Dirty Read", "Non-Repeatable Read", "Phantom Read" 문제가 발생하지 않지만, 동시성이 감소될 수 있습니다.
'Spring Framework' 카테고리의 다른 글
Spring 공통적인 작업 처리를 위한 HandlerInterceptor (0) | 2022.09.01 |
---|---|
Spring 다국어 적용하는 방법들 (0) | 2022.08.31 |
Spring 스프링 JDBC (feat. JdbcTemplate) (0) | 2022.08.26 |
Spring 프로퍼티 파일 ( .properties, .yaml), 프로파일(profiles) (0) | 2022.08.25 |
Spring @ComponentScan, @Import, @Enable (0) | 2022.08.24 |