728x90

 

스프링 프로젝트를 시작하면서 공부할게 정말 많다! 

그래도 좋은 점은 어떻게 구현해야하는지 어떤 것이 좋은지 알아가는 시간이 즐겁다

(그만큼 진도가 조금 느리기도 함..)

 

요청이 실패하다가 성공했을 경우 pk인 id값이 autoincrement로 요청이 실패한 횟수만큼 증가해있었다.

이 점을 막을 수 있으려나 고민하다가

(결론적으로 말하면 auto_increment 트랜잭션을 롤백할 수 없기 때문에 값 사이의 갭이 생기는 건 어쩔 수 없다고.. sql 설정을 바꿔주는 방법이 있긴한데 로직으로 처리하고 싶었어서 그것은 스킵! 이것도 할말이 많음)

 

예외처리를 먼저 하게 되었다

 

java.sql.SQLException: Cannot add or update a child row: 
a foreign key constraint fails 
(messycloset.usercloset, CONSTRAINT usercloset_ibfk_1 FOREIGN KEY (clothes_id) 
REFERENCES Clothes (clothes_id))

 

발생한 예외는

유효하지 않은 외래 키값을 가져와 insert하려고 해서 문제가 생긴 것이었다.

요청을 잘못해서 DAO에서 insert를 못했는데

컨트롤러에서는 문제가 없다고 api 응답이 성공적으로 postman에 찍혀서 프론트에서는 감감무소식..안돼~

개인 프로젝트가 그렇듯 하나씩 살을 붙여나갔다.

 

내가 구축한 계층(백엔드만 보자면)은

DAO - Service - Controller 

이렇게 되는데 예외를 어떻게 넘겨주고 잡아서 처리할지 고민이 되었다

 

고민 끝에 

DAO에서 exception을 service layer에 던지고 

service layer에서도 controller 계층으로 던져서 

controller에서 에러코드에 따라 다른 응답이 되도록 처리했다.

 

구현하기 앞서서 예외 Exception에 대해 공부를 하는 

시간을 가졌는데 이 내용을 정리해보고자 한다!!

 

출처 : https://www.javamadesoeasy.com/2015/05/exception-handling-exception-hierarchy.html

 

" Throwable 클래스는 Java 언어의 모든 오류 및 예외의 슈퍼클래스입니다. 이 클래스(또는 해당 하위 클래스 중 하나)의 인스턴스인 객체만 Java Virtual Machine에 의해 던져지거나 Java throw 문에 의해 던져질 수 있습니다. 마찬가지로 이 클래스 또는 해당 하위 클래스 중 하나만 catch 절의 인수 유형이 될 수 있습니다. " 출처: https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html

 

예외의 종류

Throwable 클래스 아래에 

Error 클래스와 Exception 클래스가 나뉘어져 있다

 

 

이렇듯 Error 에러는 

즉시 체크가 되지 않고 실행중에  unchecked / runtime exception

문제가 발생한다. 그래서 언제 발생할지 알 수 없으니 예외처리와 같이 처리를 할 수도 없다

에러가 나지 않도록 유념해야한다

 

이처럼 시스템 상의 문제가 발생하는 것으로 보통 메모리 부족, 스택오버플로우와 같은 복구할 수 없는 것들을 말한다.

 

Exception 예외는

컴파일로 즉시 체크가 되어서 checked / compile exception

바로 알려준다. 

그래서 throw를 하든 try catch를 하든

예외처리를 해야한다. 

 

예외를 다루는 방법

1. 예외를 회피한다. throw 쓸거니까, 예외던질거니까 나 호출하는 니가 대신 해결해~~

 

내가 선택한 방법이다. 기존에 있던 try catch를 지우고 

dao에서 예외를 처리하지 않고  throw로 던졌다.

 

그래서 dao를 사용하는 service계층에서 예외를 받았고 이걸 다시 컨트롤러로 throws 했다.

 

참고로 SQLException이

RuntimeException가 아니라 checked exception로 분류되는 이유는

데이터베이스 연산 중 발생할 수 있는 예외를 개발자가 바로 명시적으로 처리하도록 하기 위해서이다. 

checked exception 은 컴파일러가 예외를 감지해서 개발자에게 예외처리를 강제하도록 한다. 

그래서 try catch나 throws로 명시적 예외처리하지 않으면 컴파일 오류난다.

 

2. 예외를 처리한다.

 

controller 계층에서 service내 함수를 호출하는데 이때

그쪽에서 던진 예외를 받게된다. catch로 잡았다.

 

 

SQLExceptionHandler라는 클래스를 만들어서 

받은 예외코드에 따라 응답메시지로 보낼 문구를 리턴되도록 했다.

 

 

 

 

3. 예외를 바꿔준다.(예외전환)

 

SQLException 중 어떤 에러인지 다른 에러로 바꿔서 파악할 수 있게 해주는 방법

public void add(User user) throws DuplicateUserIdException{
	try{
	    // add 작업
	} catch(SQLException e){
	    if(e.getErrorCode()==중복코드){
	        throw new DuplicateUserIdException(e);
	    } else{
	        throw e;
	    }
	}
}

 

getErrorCode 메서드 같은 경우, DB 제조사별로 제각각의 에러코드를 반환한다는 점 때문에 DB에 독립적인 프로그래밍이 거의 불가능하다.  출처: https://joont.tistory.com/157 [Toward the Developer:티스토리]

 

내가 짠 코드도 같은 문제가 있다. 에러코드가 문자 자체가 같아야 그 경우에 맞게 예외를 다루고 있어서 

orm 라이브러리를 변경해 다른 에러코드가 나오면 해결이 힘들어진다.

추후에 어노테이션을 사용해 이 부분을 변경해봐야겠다!

 

스프링에서는 이런 걱정을 위해 인터페이스를 통한 독립적인 예외처리가 가능하도록 추상화를 해 두었다고 한다.

JDBC , jpa 등 DAO에 쓰이는 라이브러리가 달라서 예외 클래스도 다른데

다른 예외를 발생시키지만 공통된 상위 예외 DataIntegrityViolationException클래스를 가지도록 해두었다.

(어떻게 사용하고 처리할지는 아직 더 공부해봐될 부분이다!)

 

예외를 다루는 좋지 않은 방법

 

1. 무작정 try catch

 

예외를 잡고 적절하게 처리를 해야하는데

아무 처리 없이 catch로 잡는다면

의미없는 코드가 되고 예외 발생의 원인을 찾기도 어렵다. 차라리 throws하는게 낫다고 한다.

 

2. 무작정 throws

 

마찬가지로 계속해서 예외를 처리하지 않고 넘기기만 한다면

예외가 어디서 발생했는지 파악하기 힘들다. 

throws Exception이 발생했구나.. 그래서..? 예외..? 그래서?

이렇게 된다.

 

 

예외를 다룰 때 주의사항

1. 예외타입!

 

예외를 catch로 잡아서 처리하려는데

잡으려는 예외가 아닌 다른 예외 타입으로 지정해주면 

예외는 잡히지 않는다..

 

+

코드를 수정하다가 분명 내가 컨트롤러에서 요청이 잘못되었다는 bad request로 상태를 담아 

응답으로 보냈었는데

막상 프론트에서 열어본 응답값의 상태는 500 internal server에러였다

아까까지만 해도 400 bad request 으로 잘찍혔는데 무슨일이야? 뭐를 수정했지?

 

알고보니~~

 

 

서비스

catch (SQLException e) {
           System.out.println("e.getSQLState() 여긴service = " + e.getSQLState());
           throw new RuntimeException(e.getCause());
       }

 

컨트롤러

@PostMapping
public ResponseEntity<String> addCloset(@RequestBody UserCloset userCloset) {
    try {
        closetService.addCloset(userCloset);
        return ResponseEntity.ok("Item added successfully");
    } catch (SQLException e) {
        System.out.println("컨트롤러 catch= " + e.getMessage());
        String errorMessage = SQLExceptionHandler.handleSQLException(e);
	return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
    }
}

 

 

DAO에서 SQLException으로 넘겨준 에러를

service 계층에서 받아서 runtime exception을 발생시켜 보내고

컨트롤러에서 SQLException으로 받으려고 하는 이상한 짓을 하고 있었다..

 

500 서버 오류는 주로 서버에서 요청을 처리하는 동안 예상치 못한 상황이 발생했을 때 일어난다.

런타임 오류를 발생시켰는데 SQLException으로는 해결이 안되어 500 에러가 떴던 것이다.

 

 

RuntimeException은 언체크 예외(unchecked exception)이기 때문에, 컴파일러가 강제로 예외 처리를 요구하지 않는다.

따라서 예외를 던질 때 try catch 블록으로 묶지 않아도 되고, throws 선언도 필요하지 않는다.

 

하지만 아래코드의 경우에서

SQLExceptionRuntimeException의 하위 클래스가 아니기 때문에

이 코드는 컴파일 오류를 발생시키는 것을 볼 수 있다.

 

 Exception 예외타입은 상위 클래스이기 때문에 처리할 수 있다.

 

 

이처럼 예외를 잡아서 처리하려고 할 때

적절한 예외 클래스를 사용해야한다. 

 

 

 

 

그렇다면 RuntimeException은  언제 쓰일까?

 

 

아래 출처 chatgpt: 

RuntimeException 및 그 하위 클래스들은 주로 프로그래머의 실수에 기인하는 예외 상황이나 논리 오류를 나타내는 데 사용됩니다. 이러한 예외는 주로 프로그램의 로직이나 흐름에 오류가 있는 경우 발생하며, 컴파일러가 강제적인 예외 처리를 요구하지 않기 때문에 비교적 자유롭게 사용됩니다.

여러 상황에서 RuntimeException을 사용할 수 있습니다:

  1. 논리 오류(Legal Argument): 예를 들어, 메소드에 잘못된 인수가 전달된 경우 또는 배열에서 범위를 벗어나는 인덱스로 접근할 때 발생할 수 있습니다.
  2. 널 포인터 예외(NullPointerException): 객체가 null인 상태에서 객체의 메소드나 속성에 접근하려고 할 때 발생합니다.
  3. 캐스팅 오류(ClassCastException): 잘못된 형변환이 일어날 경우 발생합니다.
  4. 인덱스 오류(ArrayIndexOutOfBoundsException): 배열이나 리스트에서 범위를 벗어나는 인덱스로 접근할 때 발생합니다.

다만, 이러한 예외를 남용하지 않도록 주의해야 합니다. 예외는 예상치 못한 상황에 대한 메커니즘이므로, 합리적인 이유 없이 일반적인 흐름 제어를 위해 예외를 사용하는 것은 권장되지 않습니다. 일반적인 코드 로직에서는 명시적인 조건문 등을 활용하여 예외를 최소화하고 프로그램의 안정성을 높이는 것이 좋습니다.

 

그렇구나..

 
SQLException에러를 갑자기 runtime으로 던진 이유는 SQLException을 처리하지 못했을 경우 
컴파일 오류가 생기니 runtimeException을 발생시킨다라는 대목을 읽고 저렇게 짰었는데 역시 모르고 짜니까 문제가 생긴다. 
 
 

 

2. try-catch 그리고 finally

신기한게 return 을 쓰면서 finally를 하면 덮어씌워지기가 된다. 

return 이 없을 때는 try 블록을 실행하고 finally블록 실행이 잘되는데

finally를 쓸 때 주의해야하는 부분이다.

 

 

 

 

도움 많이 받은 자료들:

https://joont.tistory.com/157

 

예외처리, 스프링 예외처리

이번에는, 프로그램을 만들때 중요하지만 대부분의 개발자가 귀찮아 하는 예외처리에 대해 알아보겠습니다.잘못된 예외처리는 찾기 힘든 버그를 낳을 수 있고, 더욱 난처한 상황을 만들 수 있

joont.tistory.com

https://velog.io/@coalery/finally-evaluation-with-spec

 

Finally... 어라?

finally가 일으키는 이런저런 기묘한 동작! ECMAScript 명세와 함께 알아봅니다.

velog.io

https://youtu.be/bCPClyGsVhc?feature=shared

 

728x90

+ Recent posts