Java
-
ElasticSearch 를 사용하기 위해 SSL/TLS인증서 추가방법2023.11.30
-
[Java] OOP 4대원칙 및 SOLID원칙2023.04.09
-
[Java] equals,hashCode2023.04.09
-
[Java] java.lang 패키지2023.03.12
-
[Java] Test코드에서 Assertions를 통해 오류 잡기2023.03.12
-
[Java] Error And Exception2023.03.12
ElasticSearch 를 사용하기 위해 SSL/TLS인증서 추가방법
SSL/TLS 인증 추가하기
https
를 사용하는 ElasticSearch 를 사용해 데이터를 조회하려 했는데
unable to find valid certification path to requested target
와 같은 오류가 발생했다.
해당오류는 SSL
인증서가 없기 때문에 발생한 오류다. 따라서 SSL
인증서를 추가해야하며 방법은 아래와 같다.
1. cacert 에 import 하기
기본적으로 SSL
인증서를 등록하기 위해선 당연히 SSL
인증서가 필요하다.
docker
를 통해 ElasticSearch
를 실행했다면, 아래와 같은 명령어로 인증서를 복사할수 있다.
docker cp [ElasticContainerID]:/usr/share/elasticsearch/config/certs/http_ca.crt .
위 명령어를 통해 http_ca.crt
인증서를 복사했다면 해당 인증서를 JAVA
의 keystore
에 등록해줘야한다.
keystore
에 등록하는 방법은 cacerts
라는 파일에 import
를 해주면 된다.
[sudo] keytool -import -alias [별칭이름] -keystore [cacerts 경로] -file [http_ca.crt 경로]
나같은 경우에 cacerts
의 경로는 아래와 같았다.
/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home/lib/security/cacerts
참고로 권한이 없다고하면 sudo
를 붙여 명령어를 실행해주자.
cacerts
에 import
를 하기 위해선 비밀번호가 필요한데 바꾸지 않았다면 changeit
이다.
cacerts
파일에 ssl
인증서를 import
했다면, 이젠 intellij 에서 vm
옵션을 통해 아래 명령어를 추가해주면 된다.
2. Intellij 의 VM 옵션 추가하기
-Djavax.net.ssl.trustStore=[cacerts 경로] -Djavax.net.ssl.trustStorePassword=[cacerts 비밀번호]
이제 다시 실행해보면 https
를 사용하는 elasticSearch
로부터 정상적인 응답을 받는것을 확인할수 있다.
'Java' 카테고리의 다른 글
[Java] OOP 4대원칙 및 SOLID원칙 (0) | 2023.04.09 |
---|
[Java] OOP 4대원칙 및 SOLID원칙
Solid
SRP(Single Responsibility Principle) 단일책임원칙
클래스나 모듈은 변경할 이유가 하나만 있어야 한다. 즉, 클래스는 하나의 책임 또는 작업만 가져야 하며, 클래스를 변경할 수 있는 다른 책임이 없어야 한다.
결과적으로 SRP원칙을 잘따른 클래스는 변경사항에 대한 파급 효과가 적으며 , 변화에 잘 대응할수 있다.
❗이때 무조건적으로 하나의 클래스에 하나의 기능만 있으란건아니고 해당클래스에 너무 위배되는 기능 (ex) 옷장이 옷을벗는다) 이 없으면 된다.
OCP(Open Closed Principle) 개방 폐쇄 원칙 (추상화, 다형성)
개방 폐쇄 원칙은 높은 응집도와 낮은 결합도 라는 원리로 설명할 수 있다.
즉 확장에 열려있고, 변경에는 닫혀있어야한다.
이말은 새로운 변경사항은 유연하게 추가 또는 수정하되 객체에 존재하는 기존의 코드에는 변경이 일어나지 않도록 설계해야한다.
결론적으로 개방-폐쇄 원칙을 수용하는 코드는 컴파일타임 의존성을 수정하지 않고도 런타임 의존성을 쉽게 변경할수 있는 코드를 말한다. 위의 원칙을 준수하면 새로운 기능이 필요할때 기존의 코드를 변경하지 않고 기능을 추가할수 있다.
LSP(Liskov Substitution Principle) 리스코프 치환 원칙 (인터페이스)
객체 프로그램의 정확성을 깨지 않으면서 하위 타입의 인스턴스로 바꿀수 있어야 한다.
다형성을 지원하기 위한 원칙이다.
인터페이스의 메서드를 사용한다 할때, 어떤 구현체를 사용하든 호출부에서 기대하는 대로 동작되어야 한다.
상속을 통한 재사용은 기반 클래스와 서브 클래스 사이에 IS-A관계가 있을 경우로 제한해야하며 그외의 경우는 합성을 이용해 재사용을 해야 한다.
ISP(Interface Segragation Principle) 인터페이스 분리 원칙
범용 인터페이스 하나보다는 특정 클라이언트를 위한 여러 개의 인터페이스 분리가 더 좋다.
이때 인터페이스가 구체화되면서 클라이언트에 변화를 일으키면 안된다.
예를들어 운전자가 자동차를 운전한다 라는 명제를 객체간 관계로 비교하면 자동차에 대한 인터페이스, 운전자에 대한 인터페이스를 각각 분리한다. 이렇게되면 운전자는 택시기사, 버스기사 등..이 될수있고 자동차는 택시,버스 ..등 이 될수있다. 즉 확장성이 커진다.
DIP(Dependency Inversion Principle) 의존관계 역전 원칙
- 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안된다.
- 추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항은 추상화에 의존해야 한다.
위 2가지 사항을 의존성 역전 원칙이라 한다. 이름이 의존성 역전 원칙인 이유는 절차형 프로그래밍(상위 모듈이 하위 모듈에 의존)과는 의존성의 방향이 반대방향이라 그렇다고 한다.
OOP 4원칙
캡슐화
데이터와 데이터를 활용하는 함수를 외부에 들어나지 않게 한다.
즉 데이터를 외부에서 접근하지 않고 함수를 통해서만 접근할수 있도록 한다.
캡슐화를 통해 객체의 상태가 실수로 수정되는 것을 방지하고 코드 유지보수성을 개선하는 도움을 준다.
상속화
상속은 클래스가 다른 클래스로부터 속성 및 메서드를 상속할 수 있는 메커니즘이다.
다른 클래스에서 상속하는 클래스를 서브클래스 또는 파생 클래스라고 하고, 상속하는 클래스를 수퍼클래스 또는 베이스 클래스라고 한다.
상속을 사용하면 코드를 재사용할 수 있으며 클래스를 관계에 따라 계층 구조로 구성하는 데 도움이 되며, 상속은 캡슐화를 유지 하는데도 도움을 준다.
추상화
인터페이스, 추상클래스, 추상 메소드를 통해 객체가 가진 특성중 필수적인 속성만 객체로 묘사하고 유사성만을 표현하며 세부적인 상세 사항은 객체에 따라 다르게 구현될수 있도록 한다.
다형성
하나의 메세지에 대해 각 객체가 가지고 있는 고유한 방법으로 응답할수 있는 능력
- 오버라이딩: 상위클래스가 가지고있는 메서드를 하위 클래스가 재정의 하여 사용
- 오버로딩: 서로다른 파라미터를 통해 이름이 같은 메서드 를 여러개 가진다.
'Java' 카테고리의 다른 글
ElasticSearch 를 사용하기 위해 SSL/TLS인증서 추가방법 (0) | 2023.11.30 |
---|
[Java] equals,hashCode
equals,hashCode
hash 값을 사용하는 Collection에서 논리적으로 같은 객체인지 확인하는 과정은 위 그림과 같다. hashCode()에서 두 객체가 같다 판단한후 equals()메서드에서 두객체가 같다 판단한다면Collection은 동등객체라 판단한다.
HashSet<Point> points = new HashSet<>();
points.add(Point.of(1, 1));
points.add(Point.of(2, 2));
points.add(Point.of(3, 3));
Point pointToRemove = Point.of(1, 1);
points.remove(pointToRemove);
예를들어 위 코드에서 remove()메서드를 사용하더라도 points HashSet에 존재하는 첫번째 Point객체를 삭제할수없다. 그 이유는 우리가 Point클래스안에 equals() 와 hashCode()메서드를 재정의 하지않아 Object에 존재하는 equals,hashCode 메서드를 사용하며, Object에 존재하는 equals 메서드는 메모리주소가 동일한 객체를 같은객체로 판단한다.
하지만 points에 존재하는 Point.of(1,1)과 pointToRemove는 같은 메모리주소에할당된 객체 가아니기 때문에 위와같이 remove메서드를 통해 HashSet에서 좌표가 1,1 인 Point 객체를 삭제할수 없다.
따라서 위와같은 상황에서 우리가 원하는 결과값을 얻기위해선 equals 와 hashCode메서드를 재정의 해야한다.
class Point {
private final int x;
private final int y;
...
@Override
public boolean equals(Object o) {
if (this == o) { //비교대상과 Remove할객체의 주소값이 같은가?
return true;
}
if (o == null || getClass() != o.getClass()) { //Remove할 객체가 null이거나 비교대상과 Remove할 객체의 클래스가 다른가?
return false;
}
Point point = (Point) o;
return x == point.x && y == point.y;//비교대상과 Remove할 대상의 x,y 좌표값이 같은가?
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
public void hashSet() {
HashSet<Point> points = new HashSet<>();
points.add(Point.of(1, 1));
points.add(Point.of(2, 2));
points.add(Point.of(3, 3));
Point pointToRemove = Point.of(1, 1);
for (Iterator<Point> iterator = points.iterator(); iterator.hasNext();) {
Point point = iterator.next();
if (point.equals(pointToRemove)) {
iterator.remove();
}
}
}
Object 클래스에 존재하는 equals 메서드와 hashCode메서드를 override해 우리가 원하는 결과를 기대할수있게된다.
❗❗만약 위와같은 equals메서드만을 재정의 하고 hashCode()메서드를 재정의 하지 않는다면 어떻게 될까?
위와같은 상황에서 equals메서드만 재정의한다 가정하면 우리가 원하는 remove 동작을 기대할수있다. 왜냐하면 우린 위 코드에서 HashCode를 거치지 않고 point.equals(pointToRemove) 를 통해 우리가 삭제하고싶은 객체와 points안에 있는 객체중 같은 객체라 판단되는것을 삭제하기때문이다. 하지만points.add()를 하는상황에서 문제가 발생한다.
만약 hashCode()를 재정의 하지않고 points.add(Point.of(2,2))를 하게된다면 Collection이 동등한 객체를 판단하기위해 hashCode()를 호출후 equals()를 호출한다. 이때 재정의되지않은 hashCode()는 Object에 존재하는 hashCode()를 실행하게 된다. 즉 Object에 존재하는 hashCode()는 객체의 메모리주소값을 가지고 같은값인지 판단하지만 우리가 새롭게 추가할 Point.of(2,2)는 기존에 추가된 Point.of(2,2)와 메모리 주소값이 다르기때문에 equals() 메서드로 가기전에 이미 동등한 객체가 아니라는 결론이 나, set값에 같은 x,y좌표를 가진 Point객체가 2개 저장되게 된다.
따라서 equals()메서드를 override할땐 hashCode()메서드도 override 해주자 (hash 값을 사용하는 Collection이 아니라면 hashCode()를 재정의 하지 않아도 문제가 생기지 않을수 있지만 프로그래밍은 혼자하는게 아니므로 되도록 equals()와 hashCode()메서드를 같이 재정의 해주자)
When you add an object to a hash-based data structure like a HashSet
, HashMap
, or Hashtable
, the data structure first computes the hash code of the object by calling its hashCode()
method. The data structure then uses this hash code to determine the bucket location for the object.
Reference
'Java > Java 개념' 카테고리의 다른 글
[Java] java.lang 패키지 (0) | 2023.03.12 |
---|---|
[Java] Test코드에서 Assertions를 통해 오류 잡기 (0) | 2023.03.12 |
[Java] Error And Exception (0) | 2023.03.12 |
[Java] ArrayList 출력 방법 3가지 (0) | 2023.03.12 |
[Java] Object를 String타입으로 변환 (0) | 2023.03.12 |
[Java] java.lang 패키지
java.lang 패키지
class HelloWorld{
public static void main(String[] args){
System.out.println("Hello, World!");
}
}
위와같은 class가 있다 가정해보자.
이때, 우린 아무런 package도 import하지 않았는데 System클래스에 존재하는 println메서드를 사용한다.
이러한 이유는 JAVA에서 필요한 클래스들이 모여있는 패키지인 java.lang package는 import문이 따로 없어도 자동적으로 프로그램에 포함이 되기 때문이다.
java.lang에 존재하는 클래스
Object
: The root class of the Java class hierarchy.String
: A class that represents a sequence of characters.System
: A class that provides access to the standard input, output, and error streams of the program.Math
: A class that provides methods for performing basic mathematical operations.Integer
,Long
,Float
,Double
,Short
,Byte
: Wrapper classes for primitive types.Exception
,RuntimeException
,Error
: Classes for handling exceptions and errors.
In addition to these, the java.lang
package also contains other classes such as Thread
, ClassLoader
, Process
, and ProcessBuilder
, among others.
위 클래스들말고도 수많은 클래스가 존재한다. 아무튼 결론은 우리가 따로 import하지않아도 사용할수 있는 클래스들은 java.lang package에 존재한다고 보면된다.
'Java > Java 개념' 카테고리의 다른 글
[Java] equals,hashCode (0) | 2023.04.09 |
---|---|
[Java] Test코드에서 Assertions를 통해 오류 잡기 (0) | 2023.03.12 |
[Java] Error And Exception (0) | 2023.03.12 |
[Java] ArrayList 출력 방법 3가지 (0) | 2023.03.12 |
[Java] Object를 String타입으로 변환 (0) | 2023.03.12 |
[Java] Test코드에서 Assertions를 통해 오류 잡기
Assertions를 통해 오류를 던지는지 확인하기
org.junit.Assert 와 org.assertj.Assertions의 차이점
org.junit.Assert
: 가장 널리 사용되는 Java 테스트 프레임워크 중 하나인 JUnit 테스트 프레임워크의 일부이다. JUnit은 예상 결과를 실제 결과와 비교하여 확인할 수 있는 어설션 메서드 세트를 제공한다. 예를 들어 assertEquals(expected, actual)를 사용하여 두 개체가 동일한지 확인할 수 있다.
org.assertj.Assertions
: 반면에 org.assertj.Assertions는 비교적 최신 Java 테스트 프레임워크인 AssertJ 테스트 프레임워크의 일부이다. AssertJ도 어설션 메서드 세트를 제공하지만, 어설션 작성을 위한 보다 유창하고 직관적인 API를 제공하는 것을 목표로 한다. 예를 들어, assertThat(actual).isEqualTo(expected)를 사용하여 두 객체가 동일한지 확인할 수 있다.
그럼 둘중 무엇을 사용해야할까?
org.junit.Assert와 org.assertj.Assertions는 모두 코드 테스트를 위한 어설션 메서드를 제공하지만, AssertJ의 더 전문화된 API가 일부 테스트 시나리오에 더 나은 선택이 될 수 있다. 궁극적으로 두 프레임워크 중 하나를 선택하는 것은 개인의 선호도와 테스트 프로젝트의 특정 요구 사항에 따라 달라진다.
따라서 나는 아직 초보다 보니 org.junit.Assert 와 org.assertj.Assertions 방법 모두 사용했다.
@Test
@DisplayName("사람이 10000명일때 pointsValidate 성공 테스트")
void pointsValidateSuccessTest() {
LadderLine line = new LadderLine(10000);
line.createLine();
//1. org.junit.jupiter.api.Assertions 로 테스트
//람다식을 사용한 형태
org.junit.jupiter.api.Assertions.assertDoesNotThrow(() -> line.pointsValidate());
//람다식을 사용하지 않은 형태
org.junit.jupiter.api.Assertions.assertDoesNotThrow(new Executable() {
@Override
public void execute() throws Throwable {
line.pointsValidate();
}
});
//2. org.assertj.core.api.Assertions 으로 테스트
//람다식을 사용한 형태
org.assertj.core.api.Assertions.assertThatCode(() -> line.pointsValidate()).doesNotThrowAnyException();
//람다식을 사용하지 않은 형태
org.assertj.core.api.Assertions.assertThatCode(new ThrowableAssert.ThrowingCallable() {
@Override
public void call() throws Throwable {
line.pointsValidate();
}
}).doesNotThrowAnyException();
}
테스트한 method에 대해 설명을 하자면 pointsValidate메서드는 LadderLine객체에 존재하는 ArrayList points에 연속된 true가 저장되어있다면 IllegalArgumentException을 발생시킨다.
위 테스트는 성공 테스트이기때문에 org.junit.Assertions에서는 asseretDoesNotThrow를
org.assertj.Assertions에서는 doesNotThrowsAnyException을 사용했다. (즉 예외가 발생하면 안된다)
Excutable, ThrowableAssert를 사용한 이유
" assertDoesNotThrow(new Executable().. " 여기서 Excutable을 사용한이유는 위 사진처럼 assertDoesNotThrow는 파라미터를 Executable로 받기 때문이다.
"assertThatCode(new ThrowableAssert.ThrowingCallable... " 인 이유도 위와 동일하다. 이때 ThrowableAssert가 붙는이유는 ThrowingCallable 인터페이스를 가지고있는 클래스가 ThrowableAssert이기 때문이다.
'Java > Java 개념' 카테고리의 다른 글
[Java] equals,hashCode (0) | 2023.04.09 |
---|---|
[Java] java.lang 패키지 (0) | 2023.03.12 |
[Java] Error And Exception (0) | 2023.03.12 |
[Java] ArrayList 출력 방법 3가지 (0) | 2023.03.12 |
[Java] Object를 String타입으로 변환 (0) | 2023.03.12 |
[Java] Error And Exception
예외 계층
Error는 일반적으로 프로그램 자체에서 처리할 수 없는 심각한 문제다. Error는 일반적으로 하드웨어 또는 시스템 문제와 같은 외부 요인으로 인해 발생하며, 이로 인해 프로그램이 갑작스럽게 종료될 수 있다. Java에서 발생하는 Error의 예로는 OutOfMemoryError 와 StackOverflowError가 있다.
반면에 Exception은 프로그램 자체에서 처리할 수 있는 덜 심각한 문제다. Exception는 프로그램 로직의 오류 또는 잘못된 사용자 입력이나 네트워크 연결 실패와 같은 실행 중 예기치 않은 조건으로 인해 발생한다. Exception는 try-catch 블록을 사용하여 프로그램에서 포착하고 처리할 수 있으며, Java에서 Exception의 예로는 NullPointerException(RuntimeException의 자식 클래스)과 IllegalArgumentException이 있다.
- Exception 은 Checked Exception과 Unchecked Exception이 있다. Checked Exception은 try-catch블럭을 사용해 개발자가 예외처리를 직접해야 하지만 Unchecked Exception은 개발자가 예외를 처리해주지 않아도 컴파일 오류가 발생하지 않는다. (물론 try-catch block을 사용해서 예외를 처리해도되지만 강제되지 않는다는것이다).
- Unchecked Exception의 예로 RuntimeException이 있다. RuntimeException의 자식 클래스들 또한 Unchecked Exception이다.
예외는 발생한 곳에서 처리하지 못하면 밖으로 던져줘야 한다.
또한 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리된다.
즉 Exception 을 catch 로 잡으면 그 하위 예외들을 모두 잡을수 있으며, Exception을 throws로 던지면 그 하위 예외들도 모두 던질수 있다.
Checked Exception, Unchecked Exception
Exception을 상속받는 두 예외의 가장큰 차이는 예외를 처리할 수 없을때 해당 예외를 밖으로 던지는 부분에 있다.
Checked Exception : 해당 예외를 처리할수 없다면 반드시 예외를 던져줘야한다.
Unchecked Exception: 해당 예외를 처리할수 없다해도 예외를 던져주지 않아도 된다.
public void callThrow() throws MyUncheckedException {
repository.call();
}
이런식으로 RuntimeException을 상속받는 MyUncheckedException의 경우 unchecked Exception인 RuntimeException을 상속받으므로, 반드시 throws 를 해줄 필요는 없다.
public void callThrow(){
repository.call();
}
즉 이렇게만 써도 된다는 소리다.
Checked Exception, Unchecked Exception을 언제 사용해야 하는가?
Checked Exception 같은경우는 예외가 발생했을때 반드시 잡아서 처리해야 하는 문제일 때만 사용한다.
예를들어 계좌 이체 실패 예외 같이 매우 심각한 문제는 개발자가 Unchecked Exception으로 둘경우 예외를 놓치는 경우가 발생할수 있기 때문에 체크 예외로 만들어 두면 컴파일러를 통해 놓친 예외를 인지할수 있다.
❗️지금 까지만 보면 체크 예외가 런타임 예외(체크 예외)보다 더 안전하고 좋아 보인다. (컴파일러가 해당 예외를 잡지 않았을때 검증을 해주므로) 하지만 체크 예외를 기본적으로 사용하면 발생하는 문제들은 아래와 같다.
Checked Exception의 문제점
Repository 와 NetworkClient 는 각각 SQLException, ConnectException을 던진다. 이때 두 메서드를 호출하는 Service라는 곳에서 해당 예외를 처리할수 없다면, 두 예외가 계속해서 상위 클래스로 이동하게 된다.
결국 해당 예외는 ControllerAdvice에서 처리를 하게된다. 그후 웹 애플리케이션에서는 사용자에게 "서비스에 문제가 있습니다" 라는 일반적인 메시지를 보여준다.
즉 서비스 입장에서 알고싶지 않은 오류 내용(처리 불가능한)을 Checked Exception을 사용하므로써 강제적으로 알게된다는 문제점이 발생한다. 결론적으로 개발자가 예외를 던지는 시점에서 그것을 처리할 방법을 알 수 없는 경우 Unchecked Exception을 사용하는게 더 좋다는 뜻이다.
만약 Service, Controller 에서 처리할수 없는 (위에서 SQLException) 예외를 throws 하게 되면 어떠한 추가적인 문제점이 있을까?
가장 큰 문제점중 하나는 Service 또는 Controller에서 SQLException을 던지게 되면 해당 계층에서 java.sql.SQLException
에 의존하는 문제가 발생한다.
즉 우리가 추후 JPA를 사용하게 된다면 SQLException
에 의존하던 모든 서비스, 컨트롤러의 코드를 JPAException
에 의존하도록 고쳐야 한다.
따라서 이런 예외 하나때문에 OCP,DI를 통해 클라이언트 코드의 변경 없이 대상 구현체를 변경할수 있다는 장점이 사라지게 된다.
만약 Checked Exception을 Unchecked Exception으로 변경한다면?
만약 위처럼 SQLException을 RuntimeException을 상속하는 RuntimeSQLException
을 던지게 해보자.
이렇게 UncheckedException을 던지는 경우 해당 예외를 처리할수 없는 Service 나 Controller 에서 해당 예외에 대한 내용을 알지 못하며, ControllerAdvice에서 해당 예외를 공통적으로 처리할수 있다.
또한 Checked Exception을 사용하며 발생했던 문제, Service 나 Controller가 해당 예외에 의존하는 문제 를 모두 해결할수 있다. 이렇게 하게되면 중간에 기술이 변경 되더라도 해당 예외를 사용하지 않는 Controller나 Service의 코드는 변경하지 않아도 된다.
❗️위처럼 RuntimeException을 사용하는게 더 좋다. 다만 주의할점은 개발자가 해당 예외를 놓치지 않도록 런타임 예외에 대한 문서화를 잘 해놔야한다.
예외 포함과 스택 트레이스
예외를 전환할 때 기존 예외를 포함해야 한다. 그렇지 않으면 스택 트레이스를 확인할 때 심각한 문제가 발생한다.
스택트레이스 출력방법
void printEx() {
Controller controller = new Controller();
try {
controller.request();
} catch (Exception e) {
log.info("ex", e);
}
}
이런식으로 log를 찍을때 마지막 파라미터에 예외를 전달하면 스택 트레이스를 출력할수 있다.
기존 예외를 포함하여 예외를 던지는 방법 (exception chaining)
public void call() {
try {
runSQL();
} catch (SQLException e) {
throw new RuntimeSQLException(e); //기존 예외(e) 포함
}
}
SQLException 이 발생했을때 RuntimeSQLException을 던져주는데 이때 e 를 포함하면 RuntimeSQLException
을 catch할 때 원래의 SQLException
에 대한 정보도 함께 얻을 수 있다.
13:10:45.626 [Test worker] INFO hello.jdbc.exception.basic.UncheckedAppTest - ex
hello.jdbc.exception.basic.UncheckedAppTest$RuntimeSQLException:
java.sql.SQLException: ex at
hello.jdbc.exception.basic.UncheckedAppTest$Repository.call(UncheckedAppTest.ja
va:61)
즉 예외를 다시 던질 때 원래의 예외를 인자로 넘겨줘야 상위 레이어로 예외를 전달하면서 원래의 예외 정보를 유지할수 있기 때문에 예외를 다시 던질때 원래의 예외를 인자로 넘겨주는 것을 잊으면 안된다.
데이터 접근 예외 직접 만들기
만약 위 그림처럼 특정 데이터 베이스의 예외를 복구하고 싶다고 가정해보자.
데이터를 DB에 저장할때 Unique 가 설정되어 있는 Column에 같은 값을 넣으려 한다면 JDBC 그라이버는 SQLException
을 던진다. 그리고 이 SQLException
에는 ErrorCode라는게 들어있다.
예를들어
e.getErrorCode() == 23505
위처럼 ErrorCode가 23505 라면 키 중복 오류 라는 것을 알수있고
ErrorCode가 42000 이라면 SQL 문법 오류 라는 것을 알수 있다.
❗️해당 ErrorCode는 DB마다 다르기 때문에 DB메뉴얼을 참고하자
만약 Repository에서 SQLException 이 발생한다면 우리가 직접 예외를 따로 만들어 Checked Exception인 SQLException을 만드는게 아닌 RuntimeException을 상속 받는 예외를 만들어 던져주면 된다.
근데 앞서 말했다 싶이 DB마다 ErrorCode는 모두 다르다. 즉 23505를 통해 키 중복 오류를 catch 한다하면, 그후에 DB가 변경되었을때 해당 ErrorCode를 모두 변경해야 한다는 문제점이 생긴다. 또한 ErrorCode는 매우 많다 따라서 해당 예외가 JDBC 에서 발생할때 마다 하나하나 체크를 해서 예외를 잡아줘야하는 문제가 발생한다. 이런 문제는 어떻게 해결해야 할까?
DB 마다 다른 ErrorCode를 해결하는 방법
스프링이 제공해주는 데이터 접근 예외 계층을 사용하면 해당 문제를 해결할 수 있다.
위 사진에서 DataAccessException은 Spring이 제공하는 데이터 접근 예외 계층의 최상위 이며, DataAccessException는 RuntimeException을 상속받기 때문에 Unchecked Exception이다.
또한 스프링에서 제공하는 예외는 특정 기술에 종속적이지 않다. 즉 JDBC 기술을 사용하든 JPA 기술을 사용하든 스프링이 제공하는 예외를 사용하면 된다.
즉 예를 들어 우리가 잘못된 sql 쿼리문 작성시 Spring에서는 BadSqlGrammarException
이라는 예외를 던져주는 것이다.
void exceptionTranslator() {
String sql = "select bad grammar";
try {
Connection con = dataSource.getConnection();
PreparedStatement stmt = con.prepareStatement(sql);
stmt.executeQuery();
} catch (SQLException e) {
assertThat(e.getErrorCode()).isEqualTo(42122);
//org.springframework.jdbc.support.sql-error-codes.xml
SQLExceptionTranslator exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
//org.springframework.jdbc.BadSqlGrammarException
DataAccessException resultEx = exTranslator.translate("select", sql, e);
log.info("resultEx", resultEx);
assertThat(resultEx.getClass()).isEqualTo(BadSqlGrammarException.class);
}
}
extranslator.translate("select", sql, e)
- 첫번째 파라미터는 읽을수 있는 설명 (만약 save 기능을 하고있는 메서드에서 예외를 던지려면 "save"라 하면된다) 아무렇게나 우리가 알수 있게 설명을 적으면 된다.
- 두번째는 실행한 sql
- 세번째는 발생한 Exception
을 인자로 던져주면 resultEx을 통해 해당 예외가 어떠한 예외인지 알수 있다.
❗️결론
Service, Controller 같은 계층에서 예외 처리가 필요하다면 특정 기술에 종속적인 SQLException
과 같은 예외를 직접 사용하는 것이 아닌, 스프링이 제공하는 데이터 접근 예외를 사용하면 된다.
그니까 Repository에서 스프링이 제공하는 추상화된 예외를 던지고, Service 계층에서는 해당 예외를 catch 하면 된다.
Reference
'Java > Java 개념' 카테고리의 다른 글
[Java] java.lang 패키지 (0) | 2023.03.12 |
---|---|
[Java] Test코드에서 Assertions를 통해 오류 잡기 (0) | 2023.03.12 |
[Java] ArrayList 출력 방법 3가지 (0) | 2023.03.12 |
[Java] Object를 String타입으로 변환 (0) | 2023.03.12 |
[Java] test코드작성시 console을 통해 입력받는 방법 (0) | 2023.03.12 |