[JAVA]JAVA실무

[JAVA]실무 try-catch exception 처리

greeniti 2025. 4. 21. 21:18

실무에서는 예외를 단순히 try-catch로 처리하는 것 외에도 유지보수성, 안정성, 로깅 등을 고려한 패턴들이 많이 사용됩니다.

아래에 대표적인 Java 예외 처리 실무 패턴 입니다.


1. 예외 계층 구조 구성 (Custom Exception Hierarchy)

  • 공통 예외 슈퍼클래스를 정의하고, 세부 예외를 상속해서 사용
  • 예외의 성격(비즈니스, 시스템 등)에 따라 분류
public class AppException extends RuntimeException {
    public AppException(String message) {
        super(message);
    }
}

public class BusinessException extends AppException {
    public BusinessException(String message) {
        super(message);
    }
}

public class SystemException extends AppException {
    public SystemException(String message) {
        super(message);
    }
}

2. 예외 로깅은 반드시 Logger로! (System.out X)

private static final Logger logger = LoggerFactory.getLogger(MyService.class);

try {
    // 작업
} catch (Exception e) {
    logger.error("예외 발생: {}", e.getMessage(), e);
    throw e; // 재던지기
}

SLF4J + Logback 혹은 Log4j2 사용 추천

 


3. 예외 재던지기 (Wrapping / Rethrowing)

try {
    // DB 작업
} catch (SQLException e) {
    throw new SystemException("DB 처리 중 오류 발생", e); // 원인 포함
}

예외를 감싸서 비즈니스 계층으로 올리는 방식. 트랜잭션 처리 시 중요.

 


4. Global Exception Handler (Spring 기준)

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<String> handleBusiness(BusinessException e) {
        return ResponseEntity.badRequest().body(e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleOther(Exception e) {
        return ResponseEntity.internalServerError().body("서버 오류 발생");
    }
}

모든 컨트롤러의 예외를 한 곳에서 처리할 수 있어 유지보수가 쉬움


5. 메시지 표준화 (에러코드 + 메시지)

public class ErrorResponse {
    private String code;
    private String message;
    
    // 생성자, getter/setter
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusiness(BusinessException e) {
    return ResponseEntity.badRequest().body(new ErrorResponse("BUSINESS_ERROR", e.getMessage()));
}

클라이언트와 API 연동 시 일관성 있는 응답 제공


6. Optional & Validation 적극 사용 (예외 발생 자체를 줄임)

// Optional로 null 체크 대체
Optional<User> userOpt = userRepository.findById(id);
User user = userOpt.orElseThrow(() -> new BusinessException("사용자 없음"));

// Bean Validation
public class UserDTO {
    @NotBlank
    private String name;
    
    @Email
    private String email;
}

7. 예외를 삼키지 말고 반드시 처리 or 로깅

try {
    doSomething();
} catch (Exception e) {
    // 이런 건 지양!
    // e.printStackTrace(); ← 최소 logger 사용
    // 아무 처리 안함 → Silent Fail 위험
}

 

==> e.printStackTrace()는 가급적 사용 자제... 


요약

패턴 설명

계층적 커스텀 예외 예외를 역할별로 구분하여 관리
로깅 필수 System.out.println 대신 Logger 사용
예외 재포장 낮은 수준의 예외를 비즈니스 계층 예외로 변환
전역 예외 핸들러 예외를 통합적으로 처리
응답 메시지 구조화 API 사용자에게 명확한 에러 전달
사전 방어 Optional, Bean Validation 등으로 예외 자체 줄이기

Spring에서 DB insert, update 작업 중 예외 처리를 잘하는 방법은 트랜잭션 처리, 예외 핸들링, 로깅, 그리고 적절한 예외 재던지기를 조합해서 쓰는 겁니다. 아래에 실무에서 자주 쓰는 패턴을 예시 입니다. 


✅ 상황: 회원 정보를 DB에 저장 (insert or update)

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    @Transactional
    public void saveUser(User user) {
        try {
            if (userRepository.existsByEmail(user.getEmail())) {
                throw new BusinessException("이미 등록된 이메일입니다.");
            }

            userRepository.save(user); // insert or update (JPA 기준)
        } catch (DataIntegrityViolationException e) {
            // 제약 조건 위반 (예: UNIQUE 키 중복)
            throw new BusinessException("데이터 무결성 오류: " + e.getMessage(), e);
        } catch (Exception e) {
            // 기타 예외
            throw new SystemException("회원 저장 중 오류 발생", e);
        }
    }
}

✅ 예외 계층 예시 (실무 추천)

public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class SystemException extends RuntimeException {
    public SystemException(String message, Throwable cause) {
        super(message, cause);
    }
}

✅ 전역 예외 처리 (@RestControllerAdvice)

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<String> handleBusiness(BusinessException e) {
        return ResponseEntity.badRequest().body(e.getMessage());
    }

    @ExceptionHandler(SystemException.class)
    public ResponseEntity<String> handleSystem(SystemException e) {
        return ResponseEntity.status(500).body("서버 오류가 발생했습니다.");
    }
}

✅ 주의할 점

상황 처리 방법

@Transactional 반드시 Service 계층에 위치해야 함 (AOP 적용 가능 위치)
DB 제약조건 위반 DataIntegrityViolationException 또는 ConstraintViolationException catch
Entity 중복 insert 전에 existsBy... 또는 DB에서 예외 발생 후 처리
업데이트 실패 없는 ID로 update 시 예외 → 비즈니스 로직에서 존재 여부 체크
롤백 예외 발생 시 RuntimeException이면 자동 롤백됨 (@Transactional 기본 동작)

예시 흐름

  1. @Transactional 메서드 실행
  2. Repository.save() 호출
  3. insert/update 시 예외 발생 가능 (javax.validation, DB 제약 등)
  4. 예외 catch 후 BusinessException or SystemException으로 재던지기
  5. @RestControllerAdvice에서 일관된 API 응답 처리