커밋 이후 리스너의 DB 저장
Spring 6.1의 일반 스레드 기반 트랜잭션과 프록시 기반 AOP를 기준으로 설명한다.
정정: 이전 설명은 fallback을 혼합 호출 환경의 필수 설정으로 제시했다. 트랜잭션 안에서 발행할 수 있다면 fallback 없이 계약을 유지할 수 있다. “모든 이벤트가 등록만 된다”는 설명도 이벤트 발행 페이지에서 리스너별 조건으로 수정했다.
커밋 완료와 자원 정리는 같은 순간이 아니다
AFTER_COMMIT에서는 DB 커밋이 끝났어도 연결·영속성 컨텍스트 등 트랜잭션 자원이 아직 접근 가능할 수 있다. 이때 같은 자원을 쓰는 DB 변경이 다시 커밋될 것이라고 가정하면 안 된다. 새 DB 작업에는 명시적인 트랜잭션 경계가 필요하다. Spring 6.1.14 API
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onCompleted(JobCompleted event) {
Receipt response = remoteClient.send(event.jobId());
receiptStore.save(event.jobId(), response);
}
// 별도 Spring Bean. 호출이 프록시를 통과해야 한다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(long jobId, Receipt response) {
receiptRepository.save(new DeliveryReceipt(jobId, response));
}일반화한 구조 예제다. 원래 작업의 커밋과 후처리의 커밋은 독립적이다. 후처리 실패가 원래 작업을 롤백하지 않으므로 재시도·상태 추적 정책도 별도로 필요하다.
Async가 항상 다른 스레드를 뜻하지 않는다
ThreadPoolExecutor의 CallerRunsPolicy는 거절된 작업을 제출 스레드에서 실행한다. 종료된 실행기에서는 작업을 실행하지 않는다. 스레드 풀의 정책까지 봐야 실행 경계를 알 수 있다. JDK 17 API
커밋 처리 중 제출한 작업이 같은 스레드에서 실행될 수 있다면, 아직 정리되지 않은 원래 트랜잭션 자원도 고려한다. 다른 스레드에서 실행된다는 가정 대신 후처리 저장에 새 트랜잭션을 선언하는 편이 명확하다.
@Async("notificationExecutor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handle(JobCompleted event) {
// 새 트랜잭션 안에서 수행할 후처리
}리스너 전체에 적용하면 외부 통신 중 트랜잭션이 유지될 수 있다. 위의 별도 저장 Bean 구조는 이 시간을 줄이려는 대안이다. 데이터 접근 요구에 따라 선택한다.
프록시와 커넥션 풀이 전제다
같은 객체 내부의 자기 호출은 기본 프록시 방식에서 트랜잭션 프록시를 거치지 않는다. 따라서 해당 메서드의 전파 설정이 적용되지 않는다. 새 트랜잭션 메서드를 별도 Bean으로 두고 실제 호출 경로를 확인한다.
REQUIRES_NEW는 독립적인 물리 트랜잭션을 사용한다. 원래 자원이 유지된 상태에서 추가 연결이 필요할 수 있으므로 커넥션 풀 용량과 동시 호출 수를 함께 점검한다. Spring 전파 동작
Spring 6.1의 리스너 메서드 검증은 직접 선언한 Transactional의 전파 수준을 REQUIRES_NEW 또는 NOT_SUPPORTED로 제한한다. 모든 간접 AOP 구성까지 동일하게 탐지한다고 일반화하지 않는다. 6.1.14 검증 구현
NOT_SUPPORTED는 기존 트랜잭션을 중지하는 선택이다. 후처리 변경 여러 개를 원자적으로 확정하려는 REQUIRES_NEW의 대체라고 보면 안 된다.
타이밍 대신 조건을 고정해 검증한다
- 실제 DB와 트랜잭션 관리자·리스너를 연결한다.
- 작업을 호출 스레드에서 실행하는 실행기를 사용한다.
- 원래 트랜잭션을 커밋하며 후처리 저장을 실행한다.
- 별도 EntityManager로 후처리 결과가 확정됐는지 읽는다.
이 테스트는 인라인 실행과 저장 경계를 검증한다. 실제 실행기 포화 처리량, 외부 서버 응답, 전달 재시도를 측정한 것은 아니다. DB 없이 애노테이션만 검사하는 테스트로 저장 확정을 입증하지 않는다.