Spring Batch Tasklet 트랜잭션 내 개별 항목 실패 시 커넥션 누수 방지 방법

예외를 catch했다는 사실만으로 누수를 단정하지 않기

Tasklet 루프에서 예외를 잡고 다음 항목으로 진행하면 의도하지 않은 커밋이나 rollback-only 상태를 놓칠 수 있습니다. 그러나 catch 자체가 항상 커넥션 누수를 만드는 것은 아닙니다. Spring이 관리하는 트랜잭션과 직접 획득한 JDBC 자원의 소유권을 먼저 구분해야 합니다.

항목별 롤백의 전제

기본 TransactionTemplate의 REQUIRED는 바깥 트랜잭션에 참여할 수 있으므로 setRollbackOnly()가 해당 항목만 격리한다고 볼 수 없습니다. 독립적인 커밋·롤백이 요구되면 REQUIRES_NEW 같은 별도 경계를 선택하되, 바깥 트랜잭션의 연결이 유지된 채 추가 연결이 필요할 수 있습니다. Spring 트랜잭션 전파 문서.

TransactionTemplate itemTx = new TransactionTemplate(transactionManager);
itemTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

for (Item item : items) {
    try {
        itemTx.executeWithoutResult(status -> service.process(item));
    } catch (RuntimeException ex) {
        // 해당 내부 트랜잭션의 롤백 후 실패 항목을 기록한다.
        // 다음 항목 진행 여부는 업무 정책으로 결정한다.
        log.error("Item failed: {}", item.getId(), ex);
    }
}

이 코드는 경계 설명용 예시이며 Item·service·transactionManager는 실제 구현으로 연결해야 합니다. 내부에서 이미 커밋된 항목은 바깥 Tasklet이 실패해도 되돌아가지 않습니다. 중복 처리 방지와 재시작 기준을 함께 설계하고, 가능하면 Chunk의 skip/retry 정책으로 표현할 수 있는지도 검토합니다.

수동 반환이 필요한 경우

JdbcTemplate·MyBatis-Spring처럼 프레임워크가 관리하는 호출에 임의의 close()를 추가하지 않습니다. 직접 DataSource.getConnection()으로 얻은 연결은 try-with-resources로 닫고, 직접 DataSourceUtils.getConnection()을 사용했다면 대응하는 releaseConnection을 finally에서 호출합니다. releaseConnection은 현재 트랜잭션에 연결된 자원을 무조건 즉시 물리 종료하는 기능이 아닙니다. DataSourceUtils 공식 API.

검증할 결과

정상·예외·재시작 경로 각각에서 DB 반영 결과와 풀의 active/pending 연결이 안정되는지 확인합니다. ExitStatus 변경은 상태 보고이며 커넥션 반환이나 자동 롤백을 대신하지 않습니다. 누수 경고, 오래 실행되는 트랜잭션, 풀 고갈을 구분한 뒤 원인에 맞게 수정해야 합니다.

이어서 확인할 문제 해결 글

개별 항목 실패를 격리하는 TransactionTemplate 처리에는 개별 항목 실패 시 해당 트랜잭션만 롤백 (TransactionTemplate 사용) 글을 함께 참고하세요.

관련 글

“Spring Batch Tasklet 트랜잭션 내 개별 항목 실패 시 커넥션 누수 방지 방법”의 하나의 댓글

답글 남기기