들어가기 전..
Spring은 다양한 환경에서 일관된 트랜잭션 관리를 제공하여 비즈니스 로직과 트랜잭션 처리를 분리합니다.
이때, 일관된 트랜잭션 관리 란 데이터 접근 기술(JDBC, JPA 등)이나 여러 리소스를 사용하는 경우에도 동일한 방식으로 트랜잭션을 관리할 수 있도록 해준다는 것을 의미합니다.
즉, 비즈니스 로직 코드에는 트랜잭션 시작, 커밋, 롤백과 같은 세부 처리가 전혀 포함되지 않고, 오로지 핵심 비즈니스 로직에 집중할 수 있습니다.
Spring이 내부에서 해당 메서드 실행 전후로 트랜잭션을 자동으로 관리하여, 예외 발생 시 자동으로 롤백하는 등의 처리를 해줍니다.
예시
아래는 주문 처리 서비스를 예로 든 코드입니다:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
// @Transactional 어노테이션이 붙은 메서드는 트랜잭션 경계 내에서 실행됩니다.
@Transactional
public void createOrder(Order order) {
// 비즈니스 로직: 주문 검증, 처리 등
orderRepository.save(order);
// 추가 비즈니스 로직 실행
}
}
위 코드에서 `createOrder()` 메서드에는 트랜잭션 관리 코드(예: try-catch, commit, rollback)가 전혀 보이지 않습니다.
- 비즈니스 로직: 주문을 저장하고 추가 로직을 실행하는 핵심 기능만 포함합니다.
- 트랜잭션 처리: `@Transactional` 어노테이션을 통해 Spring이 메서드 실행 전 트랜잭션을 시작하고, 성공 시 커밋, 예외 발생 시 롤백하도록 자동 관리합니다.
이처럼, 비즈니스 로직과 트랜잭션 처리를 분리함으로써 코드가 깔끔해지고, 다양한 환경(JDBC, JPA, 글로벌 트랜잭션 등)에서 일관된 방식으로 트랜잭션을 적용할 수 있도록 Spring에서는 Transaction을 지원하고 있습니다.
이게 가능한 이유는 AOP 프록시가 `@Transactional`이 적용된 메서드를 가로채서 트랜잭션 경계를 설정하기 때문입니다. 이는 다음 블로그에서 정리해보도록 하겠습니다.
이번 글에서는 Spring의 트랜잭션 지원 모델과 로컬/글로벌 트랜잭션의 개념에 대해 알아보고자 합니다.
트랜잭션 개요
트랜잭션은 데이터베이스의 상태를 변화시키는 일련의 작업을 하나의 단위로 묶어, 모두 성공하거나 모두 실패하도록 보장합니다. 이를 통해 데이터 무결성을 유지할 수 있습니다.
전통적인 방식은 기술마다 트랜잭션 경계를 직접 설정해야 했으나, Spring은 추상화된 모델을 제공하여 이를 간소화합니다.
Spring의 트랜잭션 지원 모델
Spring은 로컬 트랜잭션과 글로벌 트랜잭션을 모두 지원하며, 단일 데이터베이스부터 여러 리소스를 사용하는 복합 환경까지 유연하게 관리할 수 있습니다.
로컬 트랜잭션 (Local Transactions)
로컬 트랜잭션이라는 단어가 어색하다고 생각했는데, 보통의 경우 다중 DB를 설정하지 않은 초기 상태의 작업 환경이라 볼 수 있습니다.
즉 로컬 트랜잭션이란 단일 리소스(JDBC, 단일 DB 등) 내에서 트랜잭션을 처리하는 것을 의미합니다.
로컬 트랜잭션의 특징은 아래와 같습니다.
- 단일 데이터베이스 작업에 적합하다.
- 기술별 구현체(JDBC, JPA 등)가 별도로 존재한다.
이때, 특징으로부터 알 수 있는 로컬 트랜잭션의 단점은 여러 리소스가 필요한 경우 트랜잭션 동기화가 불가능하여 확장성이 떨어진다는 것입니다.
그럼 이제 글로벌 트랜잭션에 대해 알아보겠습니다.
글로벌 트랜잭션 (Global Transactions)
글로벌 트랜잭션이란, 단일 데이터베이스와 같은 단일 리소스가 아닌 여러 데이터베이스나 메시지 큐 등 다양한 리소스를 하나의 트랜잭션으로 묶어 관리하기 위한 작업 환경이라고 합니다.
글로벌 트랜잭션의 특징은 아래와 같습니다.
- JTA(Java Transaction API)를 기반으로 통합 관리.
- JNDI를 통해 리소스를 검색하고 연결.
이를 통해 알수 있는 글로벌 트랜잭션의 장점은 여러 리소스를 동시에 동기화할 수 있어 복잡한 분산 환경에서 유리하다는 것이고, 단점은 그만큼 설정 및 환경 구성이 복잡하며, 성능 오버헤드가 발생할 수 있다는 문제가 있습니다.
이때 등장한 JTA와 JNDI라는 용어에 대해 잠깐 알아보고 넘어가겠습니다.
JTA(Java Transaction API)란?
JTA는 여러 리소스(여러 DB, 메시지 큐 등)에서 발생하는 작업을 하나의 트랜잭션으로 묶어서 처리하기 위한 표준 API입니다.
즉, 단일 DB에 한정된 로컬 트랜잭션이 아닌, 글로벌 트랜잭션(분산 트랜잭션)을 구현하도록 지원합니다.
JTA는 단일 자원(JDBC, 단일 DB)만 사용하면 로컬 트랜잭션으로 충분하지만, 여러 자원을 동시에 쓰는 복합 환경에서는 여러 곳의 커밋/롤백을 동기화해야 하므로 글로벌 트랜잭션이 필요하게 되면서 등장했습니다.
구성 요소
`JtaTransactionManager`
`JtaTransactionManager`는 Spring에서 JTA(Java Transaction API) 기반 글로벌 트랜잭션을 다룰 때 사용하는 `PlatformTransactionManager` 구현체입니다.
여러 자원(여러 DB, 메시지 큐 등)을 하나의 트랜잭션 범위 내에서 묶어야 할 때, Spring 애플리케이션 코드에서 선언적(@Transactional) / 프로그래밍 방식의 트랜잭션 관리가 가능하도록 도와줍니다.
주요 기능
1. `UserTransaction`과 `TransactionManager`를 활용한 트랜잭션 제어
- 내부적으로 `UserTransaction`(트랜잭션 시작/커밋·롤백)과 `TransactionManager`(트랜잭션 suspend/resume 등)을 사용하여 복수 리소스에 대한 작업을 하나의 글로벌 트랜잭션으로 묶어줍니다.
2. 스프링 표준 `PlatformTransactionManager `구현체
- 스프링의 트랜잭션 추상화(PlatformTransactionManager)를 준수하므로, 코드는 `@Transactional`만 달아주면 글로벌 트랜잭션을 손쉽게 적용할 수 있습니다.
- 로컬 트랜잭션(예: `DataSourceTransactionManager`)과의 사용 방식이 동일하므로, 코드 변경 없이 설정 교체만으로 분산 트랜잭션 전환이 가능합니다.
3. JNDI lookup 지원
- 필요에 따라, WAS(Web Application Server)가 제공하는 `UserTransaction`이나 `TransactionManager`를 JNDI를 통해 자동으로 검색(lookup)합니다.
- 별도 설정 없이도 기본 JNDI 경로(예: "java:comp/UserTransaction")를 사용해 자동 감지하도록 동작할 수 있습니다.
간단 요약
- 분산 트랜잭션(글로벌 트랜잭션): 여러 자원을 하나의 트랜잭션으로 묶을 때 사용
- 스프링 트랜잭션 추상화 준수: `@Transactional` 등과 같은 동일한 방식으로 트랜잭션 관리
- WAS 환경 통합: JNDI를 통해서 WAS에서 제공하는 JTA 기능과 연동
Transaction Manager: 트랜잭션을 전역적으로 관리(시작, 커밋, 롤백)합니다.
UserTransaction: 애플리케이션 코드에서 트랜잭션 경계를 명시할 때 사용(명시적 커밋/롤백 등)합니다.
JNDI(Java Naming and Directory Interface)란?
JNDI는 애플리케이션에서 특정 리소스(예: 데이터 소스, JMS 큐 등)를 이름을 통해 조회할 수 있도록 하는 디렉터리 서비스 API입니다.
즉, “이름→실제 객체” 의 매핑 역할을 수행함으로써 외부 리소스를 손쉽게 찾아 사용할 수 있게 해 줍니다.
구조 및 개념
WAS 단에서 Connection Pool 제어
- 서버(WAS) 측에 하나의 커넥션 풀을 두고 공유 객체로 관리
- 애플리케이션은 직접 DB에 연결을 요청하지 않고, JNDI lookup을 통해 DataSource 객체를 받아와서 Connection을 획득
주요 장점
1. DB 설정 정보 파악 용이
- DB 설정이 모두 WAS 환경 설정에 모여 있으므로, 어떤 DB들이 연결되어 있는지 WAS 설정만 봐도 쉽게 파악 가능
2. Connection Pool 효율성 극대화
- WAS에서 DB 커넥션 풀이 하나로 관리되므로, static 객체처럼 쉽게 재활용
- 새로운 커넥션을 매번 생성/종료하는 부담 감소 → 성능 향상
JDBC와 비교 시 차이점
- 하드코딩(직접 연결 정보 명시) 불필요
- 기존 JDBC 코드에서는 DB URL, 계정 정보를 코드에 직접 작성했다면, JNDI 사용 시 WAS 설정에 둔 정보를
이름
만으로 조회
- 기존 JDBC 코드에서는 DB URL, 계정 정보를 코드에 직접 작성했다면, JNDI 사용 시 WAS 설정에 둔 정보를
- 재활용에 유리 (부하 감소)
- JNDI(DataSource)를 이용해 커넥션을 재활용하므로, 별도의 커넥션 생성 부하를 대폭 줄일 수 있음
출처:
✨ 왜 JTA + JNDI가 필요할까?
1. 분산 트랜잭션 필요성
- 여러 DB나 자원을 동시에 업데이트하는 경우, 각각의 커밋·롤백이 일치해야 데이터 무결성을 보장
- JTA를 통해 글로벌 트랜잭션 범위 내에서 작업을 수행
2. WAS 관리 편의
- WAS가 JTA TransactionManager와 JNDI DataSource를 통합 관리
- 애플리케이션은 설정된 “이름”만 찾아 사용함 → 환경별 설정 변경 용이, 코드 수정 최소화
3. 효율적인 커넥션 풀
- WAS 수준의 Connection Pool을 한 번만 생성해 다양한 애플리케이션이나 컴포넌트가 공유
- 메모리 사용량 및 성능 측면에서 이점
이번에는 각 지원 모델에서 언급된 관리 방식들에 대해 간단하게 알아보겠습니다.
Spring 트랜잭션 관리 방식
Spring은 크게 두 가지 방식으로 트랜잭션 관리를 지원합니다.
선언적 트랜잭션 관리
선언적 트랜잭션이란 코드에 직접 트랜잭션 경계를 작성하지 않고, 어노테이션을 통해 트랜잭션 처리를 선언라는 것입니다.
즉, 흔히 우리가 Spring을 사용하면서 적용하는 `@Transactional` 을 말합니다.
이때, `@Transactional` 은 Spring AOP(프록시 기반)를 활용하여 메서드 실행 전후에 트랜잭션을 시작/종료 처리하게 됩니다.
프로그래밍 방식의 트랜잭션 관리
프로그래밍 방식의 트랜잭션이란 코드 내에서 직접 트랜잭션 경계를 제어한느 것을 말합니다.
이때, 스프링은 `TransactionTemplate` 을 제공합니다.
- 사용 예:
@Service
public class PaymentService {
private final TransactionTemplate transactionTemplate;
private final PaymentRepository paymentRepository;
public PaymentService(PlatformTransactionManager transactionManager, PaymentRepository paymentRepository) {
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.paymentRepository = paymentRepository;
}
public void processPayment(Payment payment) {
transactionTemplate.execute(status -> {
paymentRepository.save(payment);
// 추가 로직
return null;
});
}
}
- 이를 통해 세밀한 트랜잭션 제어가 가능하고, 선언적 방식이 어려운 복잡한 트랜잭션 처리에 사용할 수 있습니다.
사용법 정리
위의 내용에 대해 나름대로 간단하게 사용 범위를 정리해봤습니다.
- 간단한 비즈니스 로직 → 선언적 트랜잭션 관리를 기본으로 사용.
- 복잡한 트랜잭션 제어가 필요한 경우 → 프로그래밍 방식을 고려.
Reference
'개발 공부 > Spring' 카테고리의 다른 글
Spring | 🔥 Spring 프레임워크의 주요 어노테이션 (1) | 2024.09.24 |
---|---|
Spring | Spring MVC 패턴 - HTTP 요청을 받고 응답하기까지의 전 과정(2) (1) | 2024.09.05 |
Spring | Spring MVC 패턴 - MVC와 서블릿 (1) (0) | 2024.09.05 |
Spring | Spring AOP의 동작원리와 JDK Dynamic Proxy vs CGLIB Proxy 비교 및 Spring AOP와 AspectJ 비교 (0) | 2024.09.05 |
Spring Security | 로그인 방식 (0) | 2023.09.28 |