Checked Exception과 @Transactional: 초보 스프링 개발자를 위한 가이드
스프링(Spring) 프레임워크는 개발 생산성을 높이고, 복잡한 트랜잭션 관리 및 예외 처리를 간소화하는 데 강력한 도구를 제공합니다. 하지만 초보 개발자들은 Checked Exception과 트랜잭션 처리에 대해 자주 혼란스러워합니다. Checked Exception은 발생 시 반드시 처리해야 하는 특징을 가지며, 이는 코드의 안정성을 보장하지만 스프링의 기본 트랜잭션 관리 규칙과 충돌할 수 있습니다.
이 글에서는 Checked Exception과 @Transactional 어노테이션의 기본 개념을 설명하고, 트랜잭션 관리에서 Checked Exception의 중요성을 실제 애플리케이션 사례를 통해 다룹니다. 이를 통해 초보 개발자들이 보다 안정적이고 효과적으로 스프링 애플리케이션을 설계할 수 있도록 돕는 것을 목표로 합니다.
Checked Exception이란?
Java에서 Exception은 크게 Checked Exception과 Unchecked Exception(Runtime Exception)으로 나뉩니다. Checked Exception은 컴파일 시점에 반드시 처리되어야 하며, 이는 프로그램의 안정성을 높이는 데 기여합니다. 하지만 스프링 프레임워크의 트랜잭션 처리에서는 기본적으로 롤백되지 않는다는 점에서 주의가 필요합니다.
대표적인 Checked Exception
다음은 자주 사용하는 주요 Checked Exception들입니다:
1. 입출력 관련
- IOException: 파일, 네트워크 등의 입출력 오류.
- FileNotFoundException: 파일 경로가 잘못되었거나 파일이 존재하지 않을 때 발생.
- EOFException: 데이터 스트림의 끝에 도달했을 때 발생.
- InterruptedIOException: 입출력 작업이 중단되었을 때 발생.
2. 데이터베이스 관련
- SQLException: 데이터베이스 액세스 오류.
- BatchUpdateException: 대량 업데이트 중 오류.
- SQLTimeoutException: 쿼리가 시간 초과로 실패.
3. 클래스 및 리플렉션 관련
- ClassNotFoundException: 특정 클래스가 로드되지 않을 때.
- CloneNotSupportedException: 객체가 복제 불가능한 경우.
- InterruptedException: 쓰레드가 중단되었을 때.
- ReflectiveOperationException: 리플렉션 작업 중 오류.
4. 기타
- IllegalAccessException: 클래스나 멤버에 대한 접근 권한이 없을 때.
- InstantiationException: 클래스의 인스턴스를 생성할 수 없을 때.
- NoSuchMethodException: 호출하려는 메서드가 존재하지 않을 때.
Checked Exception과 @Transactional
기본적인 트랜잭션 롤백 규칙
스프링의 @Transactional 어노테이션은 기본적으로 RuntimeException 및 Error에 대해서만 트랜잭션을 롤백합니다. Checked Exception은 별도로 설정하지 않으면 롤백되지 않습니다.
이 기본 동작은 다음과 같이 조정할 수 있습니다:
@Transactional(rollbackFor = Exception.class)
public void exampleMethod() throws IOException {
// 비즈니스 로직
throw new IOException("IO 오류 발생");
}
위 코드에서 rollbackFor
속성을 설정하면 Checked Exception도 롤백 대상에 포함됩니다.
Checked Exception 롤백이 중요한 상황
1. 데이터 정합성이 중요한 경우
- 결제 트랜잭션: 결제 실패 시 포인트나 마일리지 변경 작업이 롤백되지 않으면 문제가 발생할 수 있습니다.
- 재고 관리: 주문 처리 실패 시 재고가 잘못 업데이트되는 경우를 방지해야 합니다.
2. 외부 시스템 연동
- API 통신 실패: 외부 API 호출 중 오류가 발생하면 이전 상태로 복원해야 합니다.
- 파일 전송 오류: 업로드/다운로드 실패 시 트랜잭션 롤백이 필요합니다.
- 메시지 큐 처리: 메시지 처리 실패 시 재처리를 위해 롤백해야 합니다.
3. 리소스 관리
- 파일 업로드/다운로드: 업로드 중 실패하면 파일 삭제 작업이 필요할 수 있습니다.
- DB 커넥션: 커넥션 풀의 무결성을 유지하려면 작업이 롤백되어야 합니다.
- 네트워크 소켓: 네트워크 오류 발생 시 리소스를 적절히 정리해야 합니다.
4. 보안 및 인증
- 인증서 검증: 검증 실패 시 후속 작업을 롤백해야 합니다.
- 암호화/복호화: 데이터 처리 중 오류가 발생하면 복원 불가능한 상태를 방지해야 합니다.
- 토큰 관리: 토큰 발급 및 삭제 작업이 정확히 관리되어야 합니다.
5. 배치 프로세스
- 대량 데이터 처리: 대량 처리 중 오류가 발생하면 모든 작업을 롤백해야 데이터 정합성을 유지할 수 있습니다.
- 스케줄링 작업: 스케줄러에서 발생한 오류는 다시 실행할 수 있도록 롤백해야 합니다.
- 리포트 생성: 리포트 생성 중 오류가 발생하면 중간 데이터는 롤백해야 합니다.
@Transactional은 만능이 아니다
스프링의 @Transactional은 강력하지만, 전적으로 의존해서는 안 됩니다. 다음은 주의할 점입니다:
적용 범위: @Transactional은 기본적으로 메서드 레벨에서 작동하며, 동일한 클래스 내에서 호출하는 다른 메서드에는 영향을 미치지 않습니다.
예시:
@Service public class MyService { @Transactional public void publicMethod() { privateMethod(); // 트랜잭션이 적용되지 않음 } private void privateMethod() { // 일부 작업 수행 } }
위의 경우,
privateMethod
는 트랜잭션이 적용되지 않습니다. 트랜잭션이 필요한 작업은 반드시 외부에서 호출 가능한 public 메서드로 분리해야 합니다.
프록시 기반 제한: @Transactional은 기본적으로 프록시 객체를 통해 동작하기 때문에, private 메서드나 동일 클래스 내 호출에는 적용되지 않습니다.
- 해결 방법: 트랜잭션이 필요한 메서드는 다른 빈(bean)으로 분리하여 호출합니다.
비동기 작업: @Transactional은 별도의 쓰레드에서 실행되는 비동기 작업에는 영향을 주지 않습니다.
- 예시:
위 코드에서 비동기 블록 내부의 작업은 트랜잭션의 영향을 받지 않습니다. 비동기 작업에 트랜잭션을 적용하려면 수동으로 트랜잭션 관리를 해야 합니다.@Transactional public void processData() { CompletableFuture.runAsync(() -> { // 트랜잭션이 적용되지 않음 someRepository.save(data); }); }
- 예시:
읽기 전용 트랜잭션: @Transactional(readOnly = true)를 설정한 경우 데이터베이스 쓰기 작업은 실패할 수 있습니다.
- 예시:
읽기 전용 트랜잭션은 주로 조회 작업에만 사용해야 하며, 쓰기 작업이 포함된 경우 적절히 설정을 변경해야 합니다.@Transactional(readOnly = true) public void fetchDataAndSave() { List<Entity> entities = someRepository.findAll(); entities.add(new Entity()); // 쓰기 작업 시 예외 발생 }
- 예시:
현업에서 사용하는 Checked Exception 처리 방법
Exception 변환 패턴: Checked Exception을 Unchecked Exception으로 변환하여 트랜잭션 롤백을 유도합니다.
@Transactional public void processFile(MultipartFile file) { try { // 파일 처리 로직 Files.copy(file.getInputStream(), Paths.get("/upload/" + file.getOriginalFilename())); } catch (IOException e) { throw new RuntimeException("파일 처리 중 오류가 발생했습니다.", e); } }
rollbackFor 속성 사용: 트랜잭션 롤백이 필요한 Checked Exception을 명시적으로 선언합니다.
@Transactional(rollbackFor = SQLException.class) public void saveData() throws SQLException { // 데이터 저장 로직 }
AOP 기반 예외 처리: 공통 예외 처리를 AOP로 구현하여 중복 코드를 줄입니다.
@Aspect @Component public class ExceptionHandlingAspect { @Around("execution(* com.example..*(..))") public Object handleException(ProceedingJoinPoint joinPoint) throws Throwable { try { return joinPoint.proceed(); } catch (IOException e) { throw new RuntimeException("AOP에서 처리된 IOException", e); } } }
결론
Checked Exception과 @Transactional은 스프링 애플리케이션의 안정성을 높이는 중요한 도구입니다. 초보 개발자는 기본 동작을 이해하고, 상황에 맞는 적절한 설정과 처리를 통해 데이터 정합성과 안정성을 유지하는 애플리케이션을 설계해야 합니다. 작은 차이가 큰 결과를 낳을 수 있다는 점을 항상 염두에 두고 코드를 작성하세요.
'Spring' 카테고리의 다른 글
MSA(마이크로서비스 아키텍처) vs 모놀리식 아키텍처 비교 (0) | 2025.01.09 |
---|---|
[Spring; 스프링] 직렬화와 역직렬화: Jackson ObjectMapper와 스프링부트에서의 활용 (0) | 2024.09.23 |
[Spring; 스프링] 스프링에서 `final`과 `@RequiredArgsConstructor`로 의존성 주입(DI) 쉽게 이해하기 (0) | 2024.09.23 |
[Spring; 스프링] JAR , WAR, EAR 차이점 및 특징 (0) | 2024.02.01 |
[Spring; 스프링] Spring MVC 자동구성 제어 / WebMvcConfigurer vs WebMvcConfigurationSupport / Swagger (0) | 2024.01.05 |