비동기 스레드에서 트랜잭션 관리 방법 (Java Spring)
카테고리
프로그래밍/소프트웨어 개발
서브카테고리
웹 개발
대상자
- Java Spring 프레임워크를 사용하는 백엔드 개발자
- 비동기 처리 및 트랜잭션 관리에 대한 이해가 필요한 중급 이상 개발자
- 데이터 일관성과 오류 처리가 중요한 배치 작업 시스템 설계자
- Spring AOP 및 트랜잭션 범위 개념을 활용한 실무 적용 필요자
핵심 요약
@Transactional
은 비동기 스레드 내부에서 자동으로 전파되지 않음CompletableFuture.runAsync()
같은 비동기 작업에서 트랜잭션 범위를 수동으로 설정해야 함- 트랜잭션 경계는 각 스레드 별로 독립적으로 관리해야 함
@Transactional
메서드를 스레드 내부에서 직접 호출하는 방식 권장- 자기호출(self-invocation) 시
@Transactional
이 작동하지 않음 TransactionTemplate
을 사용하여 트랜잭션을 프로그래밍 방식으로 명시해야 함
섹션별 세부 요약
1. 비동기 스레드에서 트랜잭션 관리의 핵심 문제
- 스프링 AOP 기반의
@Transactional
은 새로운 스레드에서 작동하지 않음 CompletableFuture.runAsync()
내부에서@Transactional
메서드 호출 시 트랜잭션 범위가 생성되지 않음- 트랜잭션 범위가 공유되면 데이터 불일치 발생 가능성
- 예: 하나의 스레드에서 실패 시 다른 스레드 영향을 미칠 수 있음
2. 트랜잭션 범위를 스레드 별로 분리하는 방법
@Transactional
메서드를 별도 서비스에서 호출
```java
@Service
public class ItemService {
@Transactional
public void processOneItem(Item item) {
updateDb(item); // 트랜잭션 범위 내 실행
callRemoteService(item); // 실패 시 트랜잭션 롤백
}
}
```
- 스레드 풀(
ExecutorService
) 사용 시 공용 메서드 호출 강제 @Transactional
이 적용된 메서드를 public 메서드로만 호출해야 함
3. 자기호출(self-invocation) 문제 해결 방법
TransactionTemplate
을 사용한 프로그래밍 방식 트랜잭션 관리
```java
@Service
public class BatchService {
@Autowired
private PlatformTransactionManager transactionManager;
public void processBatch(List
TransactionTemplate template = new TransactionTemplate(transactionManager);
items.forEach(item -> {
CompletableFuture.runAsync(() -> {
try {
template.execute(status -> {
updateDb(item);
callRemoteService(item);
return null;
});
} catch (Exception e) {
// 로깅 후 계속 처리
}
});
});
}
}
```
- Spring AOP의 자기호출 한계 우회
TransactionTemplate
을 통해 트랜잭션 경계를 명시적으로 설정
4. 트랜잭션 관리의 실무적 이점
- 데이터 일관성 보장
- 하나의 스레드 실패 시 다른 스레드 영향 없음
- 디버깅 및 오류 추적 용이
- 각 스레드 별로 트랜잭션 로그 분리 가능
- 시스템 안정성 향상
- 전체 배치 작업이 하나의 오류로 인해 중단되지 않음
결론
- 트랜잭션 범위는 반드시 스레드 별로 독립적으로 관리해야 함
@Transactional
은 비동기 작업 내부에서 직접 호출하는 메서드에 적용해야 함- 자기호출 시
TransactionTemplate
을 사용해 트랜잭션을 명시적으로 설정 - 에러 발생 시 로깅만 수행하고 전체 배치 작업을 중단하지 않는 전략 권장