
GIF 출처 : https://sigridjin.medium.com/spring-transaction-관리에-대한-메모-f391fd2885b4
그림 자료 출처 : 김영한 - Spring DB 1 / 2 자료
1 ) 자바의 예외 이해
2 ) 체크 예외 ( Check Exception ) 와 언체크 예외 ( Unchecked Exception )
3 ) 올바른 예외 사용

- Exception
: 애플리케이션 로직에서 사용할 수 있는 실질적 최상위 예외
-> 개발자가 다루는 예외들의 모음- Error
: 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션 복구 불가능한 시스템 예외를 의하며 해당 예외는 개발자가 처리하려 하면 안된다.
기본적으로 예외에 대하여 catch 혹은 Throw의 행동을 취할 수 있다. 만약 예외가 발생한 상황에서 예외를 잡을 수 없다면 예외를 호출한 곳에 던지는 것이 기본적인 원칙이다. 
예외를 처리하지 못할경우 마지막에 예외 로그를 출력하면서 시스템이 종료된다.
앞서 언급했듯이 개발자가 다루는 예외는 체크 예외와 언체크 예외가 존재한다. 그럼 둘의 특징과 차이점은 무엇인가?
- 체크 예외 ( Checked Exception )
: RuntimeException을 제외한 모든 Exception 하위 예외로 체크 예외는 ' 잡아서 처리하거나 ' , ' 밖으로 던지도록 선언 ' 해야 한다.- 언체크 예외 ( Unchecked Exception )
: RuntimeException과 그 하위 예외를 의미하며 말 그대로 컴파일러가 예외를 체크하지 않는다. 즉 , 체크 예외와 다르게 명시적으로 코드를 통해서 잡아서 처리하지 않아도 된다.
다음은 체크 예외와 언체크 예외의 예시를 보여주는 코드이다.
- 언체크 예외
package hello.jdbc.exception.basic;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.net.ConnectException;
import java.sql.Connection;
import java.sql.SQLException;
public class CheckedAppTest {
@Test
void checked() {
Controller controller = new Controller();
Assertions.assertThatThrownBy(()->controller.request())
.isInstanceOf(Exception.class);
}
static class Controller{
Service service = new Service();
public void request() throws SQLException,ConnectException{
service.logic();
}
}
static class Service{
Repository repository = new Repository();
NetworkClient networkClient = new NetworkClient();
public void logic() throws SQLException,ConnectException{
repository.call();
networkClient.call();
}
}
static class NetworkClient{
public void call() throws ConnectException{
throw new ConnectException();
}
}
static class Repository {
public void call() throws SQLException{
throw new SQLException();
}
}
}
체크 예외의 경우 메소드에서 모든 예외에 대하여 throws가 선언이 되어있다. 만약 SQLException , ConnectionException 예외 처럼 체크 예외를 사용하는 경우에 던지거나 잡지 않을 경우에 컴파일 오류가 발생한다.
- 언체크 예외
package hello.jdbc.exception.basic;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.net.ConnectException;
import java.sql.SQLException;
@Slf4j
public class UnCheckedAppTest {
@Test
void checked() {
Controller controller = new Controller();
Assertions.assertThatThrownBy(()->controller.request())
.isInstanceOf(Exception.class);
}
@Test
void printEx(){
Controller controller = new Controller();
try{
controller.request();
}catch(Exception e){
log.info("ex",e);
}
}
static class Controller{
Service service = new Service();
public void request(){
service.logic();
}
}
static class Service{
Repository repository = new Repository();
NetworkClient networkClient = new NetworkClient();
public void logic() {
repository.call();
networkClient.call();
}
}
static class NetworkClient{
public void call(){
throw new RuntimeConnectException("연결 실패");
}
}
static class Repository {
public void call(){
try{
runSQL();
}catch(SQLException e){
throw new RuntimeSQLException(e);
}
}
public void runSQL()throws SQLException{
throw new SQLException("ex");
}
}
static class RuntimeConnectException extends RuntimeException{
public RuntimeConnectException(String message){
super(message);
}
}
static class RuntimeSQLException extends RuntimeException{
public RuntimeSQLException(Throwable cause){
super(cause);
}
}
}
언체크 예외의 경우 던지지 않아도 컴파일 오류가 발생하지 않는다. 그럼 이런 의문이 들 수 있다. 예외는 좋지 않으니까 throws를 선언하는 체크 예외가 좋은게 아닐까?
그러나 앞서 언급했듯이 예외는 개발자가 처리할 수 없으며 발생하지 않아야한다. 또한 명시적으로 모든 예외를 던지거나 잡는 명시적인 코드는 여러 문제를 발생시킨다.
예를들어 JDBC 기술에서 JPA나 Mybatis 등 다른 기술로의 이전문제가 발생하는데 SQLException과 같은 JDBC 기술에서 사용하는 예외의 경우에도 변경이 필요하며 OCP 원칙에도 어긋나기 때문이다.
앞서 언급한 바와 같이 체크 , 언체크 예외는 서로 다른 장-단점을 지니고 있기 때문에 하나의 예외만을 사용하는 것은 지양하는 것이 좋다. 그러므로 개발자는 다음과 같은 원칙을 준수해야한다.
- 기본적으로 언체크 ( 런타임 ) 예외를 사용한다.
- 체크 예외는 비지니스 로직상 의도적으로 던지는 예외에만 사용해야한다.
- 예시
계좌 이체 실패 예외 ( 트랜잭션 실패 )
결제시 포인트 부족 예외
로그인 ID , PW 불일치 예외