4장 예외
4장에서는 JdbcTemplate을 대표로 하는 스프링의 데이터 액세스 기능에 담겨 있는 예외처리와 관련된 접근 방법에 대해 알아본다.
이를 통해 예외를 처리하는 베스트 프랙티스도 살펴본다.
모든 예외는 적절하게 복구되거나, 작업 중단 후 운영자나 개발자에게 분명하게 통보돼야 한다.
예외 블랙홀
try{
...
}
catch(SQLException e){
}
위 코드는 예외를 잡고 아무것도 하지 않는다.
이처럼 아무것도 하지 않고 별문제 없는 것처럼 넘어가 버리면 오작동이나 시스템 오류의 원인이 무엇인지 찾아내기 힘들어진다.
catch(SQLException e){
System.out.println(e);
}
catch(SQLException e){
e.printStackTrace();
}
콘솔 로그를 누군가 계속 모니터링 하는 게 아니라면 다른 로그나 메세지에 묻혀버린다.
무의미하고 무책임한 throws
public void method1() throws Exception{
method2();
...
}
public void method2() throws Exception{
method3();
...
}
public void method3() throws Exception{
...
}
기계적으로 메소드 선언에 throws Exception을 붙이면 적절한 처리를 통해 복구될 수 있는 예외상황도 제대로 다룰 수 있는 기회를 박탈당한다.
- throws Exception을 선언한 메소드를 사용하게 되면 어떤 exception 인지 모르기 때문에 throws Exception을 따라 붙이는 수밖에 없다.
예외의 종류와 특징
- Error
- java.lang. Error 클래스의 서브클래스들로, 시스템에 뭔가 비정상 적인 상황이 발생했을 경우에 사용된다.
- 자바 VM에서 발생시키기 때문에 애플리케이션 코드에서는 대응할 수 없다.
- 시스템 레벨이기 때문에 애플리케이션에서는 에러를 신경 쓰지 않아도 된다.
- Exception과 체크 예외
- java.lang.Exception 클래스와 그 서브클래스로 정의되는 예외들은 애플리케이션 코드의 작업 중에 발생했을 경우에 사용된다.
- 체크 예외: Exception 클래스의 서브클래스이면서 RuntimeException 클래스를 상 속하지 않은 것들이다. 명시적인 처리가 필요한 예외이다.
- 언체크 예외: RuntimeException을 상속한 클래스들이다.
- 체크 예외가 발생할 수 있는 메소드를 사용할 경우 반드시 예외를 처리하는 코드를 함께 작성해야 한다. 사용할 메소드가 체크 예외를 던진다면 이를 catch 문으로 잡든 지, 아니면 다시 throws를 정의해서 메소드 밖으로 던져야 한다. 그렇지 않으면 컴파일 에러가 발생한다.
- Exception 클래스의 서브클래스를 체크 예외와 언체크 예외로 구분한 것
- RuntimeException과 언체크/런타임 예외
- java.lang.RuntimeException 클래스를 상속한 예외들은 명시적인 예외처리를 강제 하지 않기 때문에 언체크 예외라고 불린다.
- 에러와 마찬가지로 런타임 예외는 catch문으로 잡거나 throws로 선언하지 않아도 된다.
- 주로 프로그램의 오류가 있을 때 발생하도록 의도된 것들이다.
- 대표적으로 NullPointerException 이나 IllegalArgumentException 등이 있다. 이런 예외는 코드에서 미리 조건을 체크하도록 주의 깊게 만든다면 필할 수 있다. 즉, 피할 수 있지만 개발자가 부주의해서 발생할 수 있는 경우에 발생하도록 만든 것이 런타임 예외이다.
예외 처리 방법
- 예외 복구
- 예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것이다.
- 예외가 처리되었으면 비록 기능적으로는 사용자에게 예외 상황으로 비쳐도 애플리케이션에서는 정상적으로 설계된 흐름을 따라 진행돼야 한다.
재시도를 통해 예외를 복구하는 코드이다.int maxretry = MAX_RETRY; while(maxretry --> 0) { try { ... // 예외가 발생활 가능성이 있는 시도 return; // 작업 성공 } catch(SomeException e) { // 로그 출력. 정해진 시간만큼 대기 } finally { //리소스반납. 정리작업 } } throw new RetryFailedException(); // 최대 재시도 횟수를 넘기면 직접 예외 발생
- 예외처리 회피
- 예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것이다.
- 즉, 예외 발생 시 알아서 던져지게 하거나,
public void add() throws SQLException{ // JDBC API ... }
- 예외를 잡은 후 로그를 남기고 다시 예외를 던지는
긴밀하게 역할을 분담하고 있는 관계가 아니라면 자신의 코드에서 발생하는 예외를 그냥 던져버리는 것은 무책임한 책임회피일 수 있다. 예외를 회피하는 것은 예외를 복구하는 것 처럼 의도가 분명해야 한다. 다른 오브젝트에게 예외 처리 책임을 분명히 지게 하거나, 자신을 사용하는 쪽에서 예외를 다루는 게 최선의 방법이라는 분명한 확신이 있어야 한다.public void add() throws SQLException{ try{ // JDBC API } catch(SQLException e){ //로그 출력 throw e; } }
- 예외 전환
- 예외 회피와 마찬가지로 예외를 복구해서 정상적인 상태로는 만들 수 없기 때문에 예외를 메소드 밖으로 던지는 것이다.
- 예외 회피와 달리 발생한 예외를 그대로 넘기지 않고 적정한 예외로 전환해서 던진다.
- 두 가지 목적으로 사용되는데,
- 첫째는 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못히는 경우에 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서다.보통 전환하는 예외에 원래 발생한 예외를 담아서 중첩 예외로 만드는 것이 좋다. getCause()메소드를 통해 처음 발생한 예외가 무엇인지 확인할 수 있고, 생성자나 initCause() 메소드로 근본 원인이 되는 예외를 넣어줄 수 있다.
catch(SQLException e){ ... throw DuplicateUserldException().initCause(e);
- catch(SQLException e){ ... throw DuplicateUserldException(e);
- public void add(User user) throws DuplicateUserldException, SQLException { try { // JDBC를 이용해 user 정보를 DB에 추가하는 코드 또는 // 그런 기능을 가진 다른 SQLException을 던지는 메소드를 호출하는 코드 } catch(SQLException e) ( // ErrorCode가 MySQL의 "Duplicate Entry(1062)“01면 예외 전환 if (e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) throw DuplicateUserldException(); else throw e; // 그 외의 경우는 SQLException 그대로 } }
- 두 번째는 예외를 처리하기 쉽고 단순하게 만들기 위해 포장하는 것이다. 주로 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용한다.EJBException은 RuntimeException 클래스를 상속한 런타임 예외이다. 이렇게 런타임 예외로 만들어서 전달하면 EJB는 이를 시스템 익셉션으로 인식하고 트랜잭션을 자동으로 롤백해준다. 이처럼 어차피 복구하지 못 할 예외라면 가능한 빨리 런타임 예외로 포장해 던지게 해서 다른 계층의 메소드를 작성할 때 불필요한 throws 선언이 들어가지 않도록 해줘야 한다.
- try { OrderHome orderHome =EJBHomeFactory.getlnstance().getOrderHome(); Order order =orderHome.findByPrimaryKey(Integer id); } catch (NamingException ne) { throw new EJBException(ne); } catch (SQLException se) { throw new EJBException(se); } catch (RemoteException re) { throw new EJBException(re); }
- 첫째는 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못히는 경우에 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서다.보통 전환하는 예외에 원래 발생한 예외를 담아서 중첩 예외로 만드는 것이 좋다. getCause()메소드를 통해 처음 발생한 예외가 무엇인지 확인할 수 있고, 생성자나 initCause() 메소드로 근본 원인이 되는 예외를 넣어줄 수 있다.
예외처리 전략
- 런타임 예외의 보편화
- 대응이 불가능한 체크 예외라면 빨리 런타임 예외로 전환해서 던지는 게 낫다!
- add() 메소드의 예외처리SQLException을 처리하기 위해 불필요한 throws 선언을 할 필요는 없으면서, 필요한 경우 아이디 중복 상황을 처리하기 위해 DuplicatedUserldException을 이용할 수 있다.
- public void add(User user) throws DuplicateUserldException, SQLException { try { // JDBC를 이용해 user 정보를 DB에 추가하는 코드 또는 // 그런 기능을 가진 다른 SQLException을 던지는 메소드를 호출하는 코드 } catch(SQLException e) ( // ErrorCode가 MySQL의 "Duplicate Entry(1062)“01면 예외 전환 if (e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) throw DuplicateUserldException(); else ~~throw e; // 그 외의 경우는 SQLException 그대로~~ throw new RuntimeException(e); // 예외 포장 } }
- 애플리케이션 예외
- 시스템 또는 외부 예외상황이 원인이 아니라 애플리케이션 자체의 로직에 의해 의도적으로 발생시켜, 반드시 catch해서 조치를 취하도록 요구하는 예외이다.
- 사용자가 요청한 금액을 은행 계좌에서 출금하는 기능을 가진 메소드의 경우,
- 정상적인 출금처리를 했을 경우와 잔고 부족이 발생했을 경우 다른 종류의 리턴값 반환
- 이 경우 명확하게 코드화하고 잘 관리하지 않으면 혼란이 생길 수 있다.
- 일괄된 예외상황에서의 결과 값에 대한 정책이 완벽하게 필요하다.
- 사전에 상수로 정의해둔 표준 코드가 필요하다.
- 메소드를 연이어 사용하는 경우 if 블록이 범벅된 코드가 이어질 수 있다.
- 코드가 지저분해지고 흐름 파악 및 이해가 힘들어진다.
- 코드는 그대로 두고, 잔고 부족과 같은 예외 상황에서 비즈니스적인 의미를 띤 예외를 던지기 (ex: 잔고부족 = InsufficientBalanceException)
- 정상적인 출금처리를 했을 경우와 잔고 부족이 발생했을 경우 다른 종류의 리턴값 반환
try { BigDecimal balance =account.withdraw(amount); ... // 정상적인 처리 결과를 출력하도록 진행 } catch(InsufficientBalanceException e) { // 체크 예외 // InsufficientBalanceException어| 담긴 인출 가능한 잔고금액 정보를 가져옴 BigDecimal availFunds =e.getAvailFunds(); ... // 잔고 부족 안내 메시지를 준비하고 이를 출력하도록 진행 }
4장의 의문
사라진 “throws SQLException”
JdbcTemplate 적용 전
public void deleteAll() throws SQLException {
this.jdbcContext.executeSql("delete from users");
}
JdbcTemplate 적용 후
public void deleteAll() {
this.jdbcContext.executeSql("delete from users");
}
SQLException은 코드 레벨에서 복구할 수 있는 방법인가?
→ 99%의 SQLException은 코드에서 복구할 방법이 없다.
그렇기 때문에 가능한 빨리 런타임 예외로 전환해줘야 한다. 스프링의 JdbcTemplate은 템플릿과 콜백 안에서 발생하는 모든 SQLException을 런타임 예외인 DataAccessException으로 포장해서 던져준다. 따라서 꼭 필요한 경우에만 DataAccessException을 잡아서 처리하면 되고 그 외의 경우에는 무시해도 된다.
- 애플리케이션 레벨에서 신경 쓰지 않기 위함
- SQLException에 담긴 다루기 힘든 상세한 예외정보를 의미 있고 일관성 있는 예외로 전환해서 추상화 하기 위함
결론

'Backend > Spring Boot' 카테고리의 다른 글
[SpringBoot] 실행 속도 더 빠르게 (0) | 2023.02.16 |
---|---|
[SpringBoot] lombok 사용 시 설정 (0) | 2023.02.16 |
[토비의 스프링 3.1] 6장 고립된 단위 테스트 (0) | 2022.12.06 |
[토비의 스프링 3.1] 6장 메소드 분리 (0) | 2022.12.06 |
[Spring] 세 가지 핵심 프로그래밍 모델 (0) | 2022.10.12 |
4장 예외
4장에서는 JdbcTemplate을 대표로 하는 스프링의 데이터 액세스 기능에 담겨 있는 예외처리와 관련된 접근 방법에 대해 알아본다.
이를 통해 예외를 처리하는 베스트 프랙티스도 살펴본다.
모든 예외는 적절하게 복구되거나, 작업 중단 후 운영자나 개발자에게 분명하게 통보돼야 한다.
예외 블랙홀
try{
...
}
catch(SQLException e){
}
위 코드는 예외를 잡고 아무것도 하지 않는다.
이처럼 아무것도 하지 않고 별문제 없는 것처럼 넘어가 버리면 오작동이나 시스템 오류의 원인이 무엇인지 찾아내기 힘들어진다.
catch(SQLException e){
System.out.println(e);
}
catch(SQLException e){
e.printStackTrace();
}
콘솔 로그를 누군가 계속 모니터링 하는 게 아니라면 다른 로그나 메세지에 묻혀버린다.
무의미하고 무책임한 throws
public void method1() throws Exception{
method2();
...
}
public void method2() throws Exception{
method3();
...
}
public void method3() throws Exception{
...
}
기계적으로 메소드 선언에 throws Exception을 붙이면 적절한 처리를 통해 복구될 수 있는 예외상황도 제대로 다룰 수 있는 기회를 박탈당한다.
- throws Exception을 선언한 메소드를 사용하게 되면 어떤 exception 인지 모르기 때문에 throws Exception을 따라 붙이는 수밖에 없다.
예외의 종류와 특징
- Error
- java.lang. Error 클래스의 서브클래스들로, 시스템에 뭔가 비정상 적인 상황이 발생했을 경우에 사용된다.
- 자바 VM에서 발생시키기 때문에 애플리케이션 코드에서는 대응할 수 없다.
- 시스템 레벨이기 때문에 애플리케이션에서는 에러를 신경 쓰지 않아도 된다.
- Exception과 체크 예외
- java.lang.Exception 클래스와 그 서브클래스로 정의되는 예외들은 애플리케이션 코드의 작업 중에 발생했을 경우에 사용된다.
- 체크 예외: Exception 클래스의 서브클래스이면서 RuntimeException 클래스를 상 속하지 않은 것들이다. 명시적인 처리가 필요한 예외이다.
- 언체크 예외: RuntimeException을 상속한 클래스들이다.
- 체크 예외가 발생할 수 있는 메소드를 사용할 경우 반드시 예외를 처리하는 코드를 함께 작성해야 한다. 사용할 메소드가 체크 예외를 던진다면 이를 catch 문으로 잡든 지, 아니면 다시 throws를 정의해서 메소드 밖으로 던져야 한다. 그렇지 않으면 컴파일 에러가 발생한다.
- Exception 클래스의 서브클래스를 체크 예외와 언체크 예외로 구분한 것
- RuntimeException과 언체크/런타임 예외
- java.lang.RuntimeException 클래스를 상속한 예외들은 명시적인 예외처리를 강제 하지 않기 때문에 언체크 예외라고 불린다.
- 에러와 마찬가지로 런타임 예외는 catch문으로 잡거나 throws로 선언하지 않아도 된다.
- 주로 프로그램의 오류가 있을 때 발생하도록 의도된 것들이다.
- 대표적으로 NullPointerException 이나 IllegalArgumentException 등이 있다. 이런 예외는 코드에서 미리 조건을 체크하도록 주의 깊게 만든다면 필할 수 있다. 즉, 피할 수 있지만 개발자가 부주의해서 발생할 수 있는 경우에 발생하도록 만든 것이 런타임 예외이다.
예외 처리 방법
- 예외 복구
- 예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것이다.
- 예외가 처리되었으면 비록 기능적으로는 사용자에게 예외 상황으로 비쳐도 애플리케이션에서는 정상적으로 설계된 흐름을 따라 진행돼야 한다.
재시도를 통해 예외를 복구하는 코드이다.int maxretry = MAX_RETRY; while(maxretry --> 0) { try { ... // 예외가 발생활 가능성이 있는 시도 return; // 작업 성공 } catch(SomeException e) { // 로그 출력. 정해진 시간만큼 대기 } finally { //리소스반납. 정리작업 } } throw new RetryFailedException(); // 최대 재시도 횟수를 넘기면 직접 예외 발생
- 예외처리 회피
- 예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것이다.
- 즉, 예외 발생 시 알아서 던져지게 하거나,
public void add() throws SQLException{ // JDBC API ... }
- 예외를 잡은 후 로그를 남기고 다시 예외를 던지는
긴밀하게 역할을 분담하고 있는 관계가 아니라면 자신의 코드에서 발생하는 예외를 그냥 던져버리는 것은 무책임한 책임회피일 수 있다. 예외를 회피하는 것은 예외를 복구하는 것 처럼 의도가 분명해야 한다. 다른 오브젝트에게 예외 처리 책임을 분명히 지게 하거나, 자신을 사용하는 쪽에서 예외를 다루는 게 최선의 방법이라는 분명한 확신이 있어야 한다.public void add() throws SQLException{ try{ // JDBC API } catch(SQLException e){ //로그 출력 throw e; } }
- 예외 전환
- 예외 회피와 마찬가지로 예외를 복구해서 정상적인 상태로는 만들 수 없기 때문에 예외를 메소드 밖으로 던지는 것이다.
- 예외 회피와 달리 발생한 예외를 그대로 넘기지 않고 적정한 예외로 전환해서 던진다.
- 두 가지 목적으로 사용되는데,
- 첫째는 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못히는 경우에 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서다.보통 전환하는 예외에 원래 발생한 예외를 담아서 중첩 예외로 만드는 것이 좋다. getCause()메소드를 통해 처음 발생한 예외가 무엇인지 확인할 수 있고, 생성자나 initCause() 메소드로 근본 원인이 되는 예외를 넣어줄 수 있다.
catch(SQLException e){ ... throw DuplicateUserldException().initCause(e);
- catch(SQLException e){ ... throw DuplicateUserldException(e);
- public void add(User user) throws DuplicateUserldException, SQLException { try { // JDBC를 이용해 user 정보를 DB에 추가하는 코드 또는 // 그런 기능을 가진 다른 SQLException을 던지는 메소드를 호출하는 코드 } catch(SQLException e) ( // ErrorCode가 MySQL의 "Duplicate Entry(1062)“01면 예외 전환 if (e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) throw DuplicateUserldException(); else throw e; // 그 외의 경우는 SQLException 그대로 } }
- 두 번째는 예외를 처리하기 쉽고 단순하게 만들기 위해 포장하는 것이다. 주로 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용한다.EJBException은 RuntimeException 클래스를 상속한 런타임 예외이다. 이렇게 런타임 예외로 만들어서 전달하면 EJB는 이를 시스템 익셉션으로 인식하고 트랜잭션을 자동으로 롤백해준다. 이처럼 어차피 복구하지 못 할 예외라면 가능한 빨리 런타임 예외로 포장해 던지게 해서 다른 계층의 메소드를 작성할 때 불필요한 throws 선언이 들어가지 않도록 해줘야 한다.
- try { OrderHome orderHome =EJBHomeFactory.getlnstance().getOrderHome(); Order order =orderHome.findByPrimaryKey(Integer id); } catch (NamingException ne) { throw new EJBException(ne); } catch (SQLException se) { throw new EJBException(se); } catch (RemoteException re) { throw new EJBException(re); }
- 첫째는 내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못히는 경우에 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서다.보통 전환하는 예외에 원래 발생한 예외를 담아서 중첩 예외로 만드는 것이 좋다. getCause()메소드를 통해 처음 발생한 예외가 무엇인지 확인할 수 있고, 생성자나 initCause() 메소드로 근본 원인이 되는 예외를 넣어줄 수 있다.
예외처리 전략
- 런타임 예외의 보편화
- 대응이 불가능한 체크 예외라면 빨리 런타임 예외로 전환해서 던지는 게 낫다!
- add() 메소드의 예외처리SQLException을 처리하기 위해 불필요한 throws 선언을 할 필요는 없으면서, 필요한 경우 아이디 중복 상황을 처리하기 위해 DuplicatedUserldException을 이용할 수 있다.
- public void add(User user) throws DuplicateUserldException, SQLException { try { // JDBC를 이용해 user 정보를 DB에 추가하는 코드 또는 // 그런 기능을 가진 다른 SQLException을 던지는 메소드를 호출하는 코드 } catch(SQLException e) ( // ErrorCode가 MySQL의 "Duplicate Entry(1062)“01면 예외 전환 if (e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) throw DuplicateUserldException(); else ~~throw e; // 그 외의 경우는 SQLException 그대로~~ throw new RuntimeException(e); // 예외 포장 } }
- 애플리케이션 예외
- 시스템 또는 외부 예외상황이 원인이 아니라 애플리케이션 자체의 로직에 의해 의도적으로 발생시켜, 반드시 catch해서 조치를 취하도록 요구하는 예외이다.
- 사용자가 요청한 금액을 은행 계좌에서 출금하는 기능을 가진 메소드의 경우,
- 정상적인 출금처리를 했을 경우와 잔고 부족이 발생했을 경우 다른 종류의 리턴값 반환
- 이 경우 명확하게 코드화하고 잘 관리하지 않으면 혼란이 생길 수 있다.
- 일괄된 예외상황에서의 결과 값에 대한 정책이 완벽하게 필요하다.
- 사전에 상수로 정의해둔 표준 코드가 필요하다.
- 메소드를 연이어 사용하는 경우 if 블록이 범벅된 코드가 이어질 수 있다.
- 코드가 지저분해지고 흐름 파악 및 이해가 힘들어진다.
- 코드는 그대로 두고, 잔고 부족과 같은 예외 상황에서 비즈니스적인 의미를 띤 예외를 던지기 (ex: 잔고부족 = InsufficientBalanceException)
- 정상적인 출금처리를 했을 경우와 잔고 부족이 발생했을 경우 다른 종류의 리턴값 반환
try { BigDecimal balance =account.withdraw(amount); ... // 정상적인 처리 결과를 출력하도록 진행 } catch(InsufficientBalanceException e) { // 체크 예외 // InsufficientBalanceException어| 담긴 인출 가능한 잔고금액 정보를 가져옴 BigDecimal availFunds =e.getAvailFunds(); ... // 잔고 부족 안내 메시지를 준비하고 이를 출력하도록 진행 }
4장의 의문
사라진 “throws SQLException”
JdbcTemplate 적용 전
public void deleteAll() throws SQLException {
this.jdbcContext.executeSql("delete from users");
}
JdbcTemplate 적용 후
public void deleteAll() {
this.jdbcContext.executeSql("delete from users");
}
SQLException은 코드 레벨에서 복구할 수 있는 방법인가?
→ 99%의 SQLException은 코드에서 복구할 방법이 없다.
그렇기 때문에 가능한 빨리 런타임 예외로 전환해줘야 한다. 스프링의 JdbcTemplate은 템플릿과 콜백 안에서 발생하는 모든 SQLException을 런타임 예외인 DataAccessException으로 포장해서 던져준다. 따라서 꼭 필요한 경우에만 DataAccessException을 잡아서 처리하면 되고 그 외의 경우에는 무시해도 된다.
- 애플리케이션 레벨에서 신경 쓰지 않기 위함
- SQLException에 담긴 다루기 힘든 상세한 예외정보를 의미 있고 일관성 있는 예외로 전환해서 추상화 하기 위함
결론

'Backend > Spring Boot' 카테고리의 다른 글
[SpringBoot] 실행 속도 더 빠르게 (0) | 2023.02.16 |
---|---|
[SpringBoot] lombok 사용 시 설정 (0) | 2023.02.16 |
[토비의 스프링 3.1] 6장 고립된 단위 테스트 (0) | 2022.12.06 |
[토비의 스프링 3.1] 6장 메소드 분리 (0) | 2022.12.06 |
[Spring] 세 가지 핵심 프로그래밍 모델 (0) | 2022.10.12 |