예외란?
개발자가 자신이 구현한 로직에서 비정상적인 상황을 예측하여 처리하는 것이다. (오류는 시스템에 비정상적인 상황이 생겼을 때 발생하는 것이다.)
예외의 종류는 컴파일 시 발생하는 컴파일 에러, 프로그램 구동 중에 발생하는 런타임 에러로 크게 2가지가 있다.
예외 클래스

다음은 예외 클래스의 계층 구조이다. 모든 예외 클래스는 Throwable 클래스를 상속받고 있다.
개발자는 Exception에 관련된 것만 처리하면 된다.
1) Checked Exception
체크 예외는 잡아서 처리하거나, 예외를 밖으로 던지는 throw 예외를 해야한다. 하지 않으면 컴파일 오류 발생!
IOException, SQLException, ClassNotFoundException 등이 포함된다.
2) Unchecked Exception (Runtime Exception)
컴파일 과정에서는 문제를 발견하지 못하고, 프로그램 실행 중에 발생하는 오류사항들을 말한다. 이 예외가 발생하면 프로그램이 종료된다.
NullPointerException, ArithmeticException 등이 포함된다.
예외 처리 방법
1) try catch
try{
}
catch(예외클래스 e) {
}
finally {
}
try에서 예외가 발생할 가능성이 있는 코드를 작성한다. try에서 예외가 발생하면 catch문에서 예외를 처리한다. finally는 예외 발생 여부와 관계없이 항상 실행한다.
2) throws
자신을 호출하는 메소드에 예외처리 책임을 떠넘기는 것이다. 기본적으로 체크 예외 전략이고, 런타임 예외는 throws 예외를 선언하지 않아도 상위로 넘어가기 때문에 선언하지 않아도 된다.
예외처리를 넘겨받은 곳은 try-catch 구문으로 예외처리를 해줘야 한다.
public class ThrowTest {
public static void main(String[] args) {
int n1 = 12;
int n2 = 0;
try {
throwTest(n1, n2);
} catch (ArithmeticException e) {
System.out.println("ArithmeticException: " + e.getMessage());
}
}
public static void throwTest(int a, int b) throws ArithmeticException{
System.out.println("throw a/b: "+ a/b);
}
}
throwTest에서 ArithmeticException 예외가 발생하면 호출한 곳(main)에서 처리를 해준다.
3) throw
throw는 개발자가 직접 예외를 발생시키고 싶을 때 사용한다. 개발자가 Exception을 따로 커스터마이징해서 만들고 그 안에 메시지를 넣어서 던져주는 방식이다.
public class ThrowTest {
public static void main(String[] args) {
int n1 = 12;
int n2 = 0;
try {
throwTest(n1, n2);
} catch (ArithmeticException e) {
System.out.println("ArithmeticException: " + e.getMessage());
}
}
public static void throwTest(int a, int b) throws ArithmeticException{
throw new ArithmeticException();
}
}
결과는 ArithmeticException : null 이다.
예외 발생과 코드의 트랜잭션
트랜잭션은 하나의 작업 단위이다.
블럭 내의 코드들이 예외가 발생해도 모두 실행되느냐, 아니면 예외가 발생하면 그 상태로 중지하느냐의 작업 단위를 개발자의 예외 처리 방법에 따라 달라지게 된다.
각 메서드에서 일일이 try-catch를 하면 상위의 메서드들의 코드는 모두 실행된다.
하지만 throws를 사용하여 예외처리를 상위 메서드에 모아서 처리하면 코드 한 곳에서 예외가 발생하면 뒤의 나머지 코드들은 실행되지 않는다.
기본적인 지식을 바탕으로 스프링 부트에서 예외처리를 다뤄보려고 한다!