Java

에러와 예외 (Error, Exception)

ysk(0soo) 2022. 11. 26. 19:29

오류(error) : 시스템에 무엇인가 비정상적인 상황이 발생한 경우. 프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우가 있는데 이러한 결과를 초래하는 원인.

  • Java에서는 주로 JVM(Java Virtual Machine)에서 발생시키는 것이고, 예외(Exception)와 반대로 Application Code에서 잡아도 처리할 제대로 방법이 없다.
  • OutOfMemoryError, ThreadDeath, StackOverflowError

에러는 발생 시점에 따라서 3가지로 분류할 수 있다.

  • Compile Error (컴파일 에러) : 컴파일 할 때 발생하는 에러. syntax error(문법 오류)도 포함됨. - 컴파일 시 발생하는 에러
    • 소스코드를 컴파일을 하면 컴파일러가 소스코드에 대해 오타Syntax Error(잘못된 구문), 자료형 체크등의 기본적인 검사를 수행하여 오류를 잡아낼 수 있다.
    • 소스코드를 컴파일러가 컴파일하는 시점에서 발생하는 에러를 컴파일 에러라 하며 이 시점에서 발생하는 문제들을 수정 후 컴파일을 성공적으로 마칠경우 클래스 파일(*.class) 파일이 생성된다.
  • Runtime Error (런타임 에러) : 프로그램 실행 도중에 발생하는 에러 - 실행 시 발생하는 에러
    • 컴파일러는 컴파일 시점에서 문법 오류나 오타같은 컴파일 시점에서 예측 가능한오류는 잡아줄 수 있지만, 실행중 발생할 수 있는 잠재적인 에러까지는 잡을 순 없다.
    • 컴파일은 완료되어서 컴파일 에러는 없지만 실행 중 발생할 수 있는 에러이다.
  • Logical Error (논리적 에러) : : 컴파일도 잘되고 실행도 잘되지만 의도한 것과 다르게 동작하는 것.
    • 컴파일도 정상적으로 되고 런타임 에러도 발생하지 않았지만 개발자가 의도한 대로 동작하지 않는 에러를 말한다.
    • API가 응답값을 주도록 했으나 응답값을 안주거나, 아무 동작을 안하거나, 버그가 생기는 등을 의미한다

예외(exception) : 입력 값에 대한 처리가 불가능하거나, 프로그램 실행 중에 참조된 값이 잘못된 경우 등 정상적인 프로그램의 흐름을 어긋나는 것

  • Java에서 Exception은 개발자가 직접 Application Code내에서 처리 할 수 있어, 예외 상황에 대해 미리 예측하여 Handling할 수 있다.
  • Exception, RuntimException, IOException 등

오류가 시스템 레벨에서 발생한다면 예외(Expceiton)은 개발자가 구현한 로직에서 발생한다.

 

런타임 시점에서 발생하는 오류는 에러(error)와 예외(exception)으로 나뉜다.

컴파일러가 소스코드 컴파일중 발생한 에러를 잡았다 해서 실행 도중에 발생할 수 있는 잠재적인 오류까지는 검사할 수 없다 . 이것이 런타임 에러이다.

런타임 에러를 방지하기 위해서는 프로그램 실행 도중 발생할 수 있는 모든 경우의수를 고려하여 이에 대한 대비를 하는것이 필요한데

자바에서는 실행 시(rumtime) 발생할 수 있는 프로그램 오류를 에러(rror)와 예외(exception) 두가지로 구분하였다

따라서 개발자는 예외 처리(exception handling)를 통해 예외 상황을 처리할 수 있도록 코드의 흐름을 바꿀 필요가 있다.

• 에러(Error): 메모리 부족(OutOfMemoryError)이나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 심각한 오류

• 예외(exception): 인자값 Null 에러, NPE(NullPointException)같은 발생하더라도 수습이 가능한 덜 심각한 오류.

 

자바 예외 클래스의 구조

자바는 실행 시 발생할 수 있는 오류들(Error와 Exception)을 클래스로 정의하였다.

  • 예외 클래스도 클래스이고, 모든 클래스의 조상은 Object 클래스이므로 Exception과 Error 클래스 역시 Object 클래스의 Object 클래스의 자손들이다.

예외 클래스들은 2그룹으로 나눌 수 있다.

  1. Exception 클래스를 상속받고 RuntimeException을 상속받지 않은 예외들 - CheckedException (확인된 예외)
  2. RuntimeException을 상속받은 예외들 - UncheckedException(확인되지 않은 예외)
  • Throwable : Java의 모든 오류 및 예외의 상위 클래스. 이 클래스(또는 해당 하위 클래스 중 하나)의 인스턴스인 개체만 JVM에 의해 throw되거나 Java throw문에 의해 throw될 수 있다.
    • 마찬가지로 이 클래스 또는 해당 하위 클래스 중 하나만 catch절로 catch할 수 있다.
    • 예외의 컴파일 타임 검사 목적을 위해, Throwable하위 클래스 도 Throwable아닌 하위 클래스는 확인된 예외로 간주한다. RuntimeExceptionError
  • Exception : 클래스 Exception과 해당 서브클래스는 애플리케이션이 파악하고자 하는 예외 조건을 보여주는 Throwable 이다.
    • 예외 클래스 및 RuntimeException을 상속받은 하위 클래스가 아닌 , 모든 하위 클래스 예외는 checked 예외이다.
      • RuntimeException은 Unchecked 예외이다.
    • 체크된 예외는 메서드 또는 구성 요소의 실행에 의해 슬로우며 메서드 또는 구성 요소의 경계 밖으로 전파될 수 있는 경우 메서드 또는 구성 요소의 throw 구로 선언해야 한다.
  • Error : 클래스 Error 는 애플리케이션이 catch 하는것을 시도해서는 안되며, 심각한 문제를 나타내는 Throwable의 하위 클래스.
    • 이러한 오류는 대부분 비정상적인 상태이다.
    • ThreadDeath 오류는 "정상적인" 조건이지만 대부분의 응용 프로그램이 오류를 catch하려고 시도해서는 안 되기 때문에 Error의 하위 클래스이다.
    • 메소드 실행 중에 발생할 수 있지만 포착되지 않은 Error의 하위 클래스를 throws 절에서 선언할 필요는 없다.
    • 즉, Error 와 Error의 하위 클래스는 컴파일 타임 예외 검사를 위해 unchecked 예외로 간주된다.

 

RuntimeException

RumtimeException은 JVM 정상 작동 중, 즉 애플리케이션 실행 시점에 발생할 수 있는 예외들의 상위 클래스이다.

  • RuntimeException은 Program 실행 중에 발생하며 System 환경적으로나, Input Value가 잘못된 경우, 의도적으로 개발자가 설정한 조건을 위배했을 때 throw 되게 하는 등, Application 실행 도중에 발생하는 Exception이다.
  • RuntimeException명시적으로 예외 처리를 하지 않아도 되는 Exception이다.
    • Exception 처리를 명시하지 않아도 Program 구동에 아무 문제가 없다.
    • 그러나 다른 문제를 야기할 수 있으니 처리할 수 있는 만큼 처리해주는 것이 좋다.

RuntimeException 과 ErrorClass들을 UnchckedException 이라고 묶으며,
RuntimeException 과 ErrorClass들을 제외한 예외 클래스들을 CheckedException로 묶는다,

 

CheckedException과 UncheckedException

CheckedException (확인된 예외)

RuntimeException을 상속받지 않은 클래스들을 CheckedExcpetion이라고 한다.

예외가 발생할 수 있는것이 확인되기 때문에 반드시 명시적으로 처리해야 한다.

CheckedException은 반드시 예외 처리 해야하는 Exception*으로, *Compile 시점에서 Exception 발생이 확인될 수 있으므로 반드시 명시적으로 처리해야 한다. 그렇지 않으면 컴파일러가 에러를 잡는다

  • 메소드 내에서 try catch를 하든, 메소드 밖으로 throws를 통해서 던져서 명시적으로 처리해야 한다.
    • 이것은 문법적으로 강제적이다.
  • CheckedException은 `Error와 RuntimeException을 상속받지 않은 Exception 들을 모두 포함한다.
    • Error, FileNotFoundException, ClassNotFoundException

Spring @Transaction 어노테이션 Rollback 기본 정책이 Unchcked Exception과 error들이다.
즉 설정을 바꾸지 않으면 CheckedException은 Transaction 처리시에 Exception이 발생해도 Rollback을 하지 않는다.

하지만 어떤 예외가 발생하면 롤백을 할 것이냐 말 것이냐는 개발자가 정하는 것이다.

  • CheckedException들이 롤백이 안되는 것이 아니다.
  • @Transactional(rollbackFor=Exception.class) 옵션으로 모든 예외에 대해서 롤백할 수 있다.
  • Checked Exception을 try-catch문으로 더 구체적인 Unchecked Exception으로 감싸주면 롤백이 가능하다.

 

UncheckedException (확인되지 않은 예외)

RuntimeException을 상속받은 모든 예외 클래스들을 UnchckedException 이라고 한다.

예외가 발생할 수 있는 것이 확인되지 않기 때문에 처리할 방법을 고민해야 한다.

  • UncheckedException은 명시적으로 예외 처리 해주지 않아도 되는 Exception으로, **Runtime 시점에서 Exception 이 발생해야 확인되고, 컴파일 시점에는 확인되지 않는 예외이다.
  • 주로 프로그래머들의 실수나 예견되지 못한 값 처리 등, 잘못된 값 입력 등에 의해서 일어나는 예외이다.
    • 그러니까 확인되지 않은 예외(UncheckedException)이다.
  • UncheckedExceptionRuntimeException을 상속받는 Exception들을 포함한다.
    • NullPointerException, ClassCastException, IllegalArgumentException

 

Spring @Trasactional 어노테이션과 Rollback에 관련된 checkedException과 UncheckedException의 고찰


스프링 트랜잭셔널 어노테이션 과 예외들의 관계들을 정리한 블로그들을 보고 백기선님 영상을 보니까 뭔가 잘못 공부하고 있단 생각을 들었다 

 

스프링프레임워크 @Transactionl 트랜잭션 사용 시 체크드 익셉션은 롤백 수행 x , 언체크드 익셉션은 롤백 수행 o

 

그런데 @Transactional 어노테이션 옵션을 보면 rollbackfor 와 같은 옵션들이 있다.

  • 특정한 예외를 잡으면 롤백이 되도록 설정하는것.

이 rollbackfor를 Exception.class 으로 잡으면 어떻게 될까?

  • 모든 Exception, 즉 CheckedException 까지 롤백을 하게 되는 것이다.

또한 noRollbackFor 라는 옵션이 있는데, 이걸로 특정 Unchecked Exception을 설정하게 되면?

  • 어떻게 될것인가?

그럼 다시 생각해보면 저 *@Transactionl 트랜잭션 사용 시 체크드 익셉션은 롤백 수행 x , 언체크드 익셉션은 롤백 수행 o *라는 말은 부정확한 의미가 된다.

Spring @Transactional의 예외 발생 시 기본 rollback을 수행하는 정책은 언체크드익셉션, 즉 RuntimeException과 그 하위 클래스들이고, 개발자가(우리) 비즈니스와 상황에 맞게 알맞게 처리할 수 있다.

개발자는 상황에 맞게 예외를 처리할 수 있어야 하며, 스프링 트랜잭션도 그렇게 할 수 있도록 옵션을 지원한다.

rollbackFor, noRollbackFor 등

다시한번 정리하자면

Spring @Transactional의 예외 발생 시 기본 rollback을 수행하는 정책은 UnchecedExceptiop, 즉 RuntimeException과 그 하위 클래스들이고, 개발자가(우리) 비즈니스와 상황에 맞게 알맞게 처리할 수 있게 rollbackFor, noRollbackFor. 옵션을 지원해 준다.

어디까지나 기본 정책이 체크드 익셉션은 롤백을 하지 않는것이고, 불가능한 것이 아니다.

스프링이 옵션을 제공하니까 우리는 그것을 알맞게 (상황에 맞게도 같은 말이 될 것 같다) 사용하면 되는 것이다.

우리는 문제는 상황에 맞게 다양하게 해결할 수 있어야 한다.

 

Checked Exception에서 Rollback이 안되는 이유

메소드 API를 설계할 때 해당 메소드 호출이 어떤 예외를 발생 시키는지 또한 API 규악의 중요한 내용이고, 때로는 그런 예외에 대한 처리를 호출자에게 강제할 수 있어야 한다는 뜻인 것 같다.

개발자가 컴파일 시 예측/처리 가능한 예외의 처리를 개발자에게 강제하기 위해 Checked Exception은 개발자가 처리하라 라는 뜻같다.

어노테이션을 사용하든 AOP에 설정을 하든 스프링은 Unchecked Exception(RuntimeException)과 Error 발생 시 롤백 적용을 기본 정책으로 한다.

@Override
public boolean rollbackOn(Throwable ex) {
    return (ex instanceof RuntimeException || ex instanceof Error);
}

스프링에서 제공하는 트랜잭션 설정 클래스 DefaultTransactionAttribute 의 메소드 rollbackOn를 보면 RuntimeException과 Error인 경우에 true를 반환한다.

또한 @Transactional 어노테이션의 옵션들을 보면

    /**
     * Defines zero (0) or more exception {@linkplain Class classes}, which must be
     * subclasses of {@link Throwable}, indicating which exception types must cause
     * a transaction rollback.
     * <p>By default, a transaction will be rolled back on {@link RuntimeException}
     * and {@link Error} but not on checked exceptions (business exceptions). See
     * {@link
     ...
     */
Class<? extends Throwable>[] rollbackFor() default {};
  • will be rolled back on {@link RuntimeException} and {@link Error} - 런타임 엑셥과 에러들 (언체크드 익셉션)
  • but not on checked exceptions (business exceptions). - 체크드 익셉션은 롤백 안함

@Transactional 어노테이션을 아무것도 설정하지 않은 default 사용하게 된다면 다음과 같이 기본 정책 적용된다

@Transactional(rollbackFor = {RuntimeException.class, Error.class})
public void save(AnyEntity entity) {
  ...
}

 

정리

컴파일 시점에~ CheckedException (확인되는 예외) UncheckedException (확인되지 않은 예외 )
확인 시점 Compile Runtime
처리 시점 반드시 예외 처리 명시적으로 하지 않아도 무관
예시 ClassNotFoundException RuntimeException, NPE

스프링 @Transactional을 사용해서 트랜잭션 안에서 예외가 발생할 시,

Rollback 처리되는 기본 정책의 예외들은 UncheckedException 이며 (RuntimeException, Error)

이 기본정책을 default로 사용하면 CheckedException들은 rollback 되지 않는다.

그러나 이 정책은 언제든지 변경할 수 있게 스프링에서
@Transactional( 옵션들 ) 에서 변경할 수 있고 개발자가 선택할 수 있으므로 상황에 맞게 적절하게 처리하면 된다.

  • noRollbackFor, rollbaclFor 등등

@Transactional API 문서