๐ Java & SpringBoot & JPA ํ๊ฒฝ์์ ๊ฐ๋ฐ์ค์.
์๋ก
Java/SpringBoot ๋ฅผ ์ฌ์ฉํ์ฌ ์ค๋ฌด๋ฅผ ํ๊ณ ์๋ค.
๊ฐ๋ฐ์ ํ๋ค๋ณด๋ฉด ์์ธ์ฒ๋ฆฌ๋ฅผ ํด์ผํ ์ผ์ด ์๋นํ ๋ง์ด ์๊ธธ ๊ฒ์ด๋ค
๋ํ์ ์ผ๋ก Java ํ๋ฉด NullPointException ์ด ์์ ๊ฒ์ด๋ค
์ ์ ํ ์์ธ์ฒ๋ฆฌ๋ฅผ ํ์ง ์๊ณ , Runtime ์๋ฌ๊ฐ ์๊ธธ ๊ฒฝ์ฐ ์ดํ๋ฆฌ์ผ์ด์
์ ์ฅ์ ๊ฐ ์๊ธธ ๊ฒ์ด๋ค
์ฌํ๋ฉด ์๋น์ค๊ฐ ์ค๋จ์ด ๋๋ ์ํฉ๋ ๋ฐ์ํ ์ ์๋ค
๊ทธ๋งํผ ์์ธ์ฒ๋ฆฌ๋ ๊ท์ฐฎ์ง๋ง ์ค์ํ ์ผ์ด๋ค. ์ํํ ํด์๋ ์๋๋ค
์ ์ํฉ์ ๋ฐฉ์งํ๊ธฐ ์ํด์ ์ฝ๋๋ฅผ ์์ฑํ ๋ ์ ์ ํ ์์ธ์ฒ๋ฆฌ๊ฐ ํญ์ ํ์ํ๋ค
์๋ ์์ ์ฝ๋๋ฅผ ๋ณด๋ฉฐ ํ๋ฒ ์์ธ ์ฒ๋ฆฌ๋ฅผ ์ ์ง์ ์ผ๋ก ๋ฐ์ ์์ผ๋ณด์.
์์๋ DB ์ ํต์ ํ๋ ์ํฉ์์์ ์์ธ ์ฒ๋ฆฌ์ด๋ค
์ผ๋จ try ~ catch ์ ๋ํ ๋ฐฉ๋ฒ์ ์ด์ผ๊ธฐ ํ์ง ์์ผ๋ ค๊ณ ํ๋ค
๋ฌผ๋ก ์ฌ์ฉํ ์๋ ์์ง๋ง ์คํ๋ง์๋ @Transactional ์ด๋ผ๋ ์์ฃผ ์ข์ ๊ธฐ๋ฅ์ด ์๋ค
๊ทธ๋ฆฌ๊ณ try~catch ๋ ์์ธ๋ฅผ ์ก์ ํ์ ํ์ฒ๋ฆฌ๋ฅผ ํ๊ธฐ ์ํด์ ์ฌ์ฉ๋๋ค. ํ์ง๋ง DB ์กฐํ์ ๊ด๋ จ๋ ์ํฉ์์ ํ์ฒ๋ฆฌ๋ ๋ถํ์ํ๊ธฐ ๋๋ฌธ์ด๋ค
ex) I/O ์์
์์ ๋๋ถ๋ถ ์ฌ์ฉํจ.
๋ณธ๋ก
@Getter
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String memberId;
private String password;
@OneToMany(mappedBy = "member", cascade = { CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true)
private List<Product> products = new ArrayList<>();
}
@Getter
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer price;
private Integer quantity;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Member member;
}
๊ธฐ๋ณธ์ ์ผ๋ก ์ ์ํฐํฐ๊ฐ ์กด์ฌํ๋ค. ์ด์ ๋ฐ๋ฅธ Repository ๋ํ ์์ฑ์ด ๋์ด ์๋ค
1. id ๋ฅผ ํตํ์ฌ ์กฐํํ๋ ์ํฉ
์๋ฌด๊ฒ๋ ๋ชจ๋ฅด๊ณ ๋ง ๊ฐ๋ฐํ ๋๋ ์๋์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์์ธ์ฒ๋ฆฌ๋ฅผ ํ๊ณ ๋ ํ๋ค.
1-1) RuntimeException ์ฌ์ฉํ ์์ธ์ฒ๋ฆฌ
@RequiredArgsConstructor
@Service
public class MemberService {
private final MemberRepository memberRepository;
public Member findMember(Long id) {
Member member = memberRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Member is Null"));
return member;
}
}
์ ์ผ ๋ง๋งํ๋ฉด์ ์ฌ์ด ๋ฐฉ๋ฒ์ค ํ๋์ด๋ค
๊ทธ๋ฆฌ๊ณ ์ ์ฝ๋๋ฅผ ํ
์คํธ ์ฝ๋๋ฅผ ํตํด ์คํ์์ผ ๋ณด๊ฒ ๋ค.
@SpringBootTest(classes = DddStartApplication.class)
class MemberServiceTest {
@Autowired
private MemberService memberService;
@Test
@DisplayName("Member ๋ฅผ ์กฐํํ๋ค.")
void findMemberTest() {
// given
Long id = 2839823L;
// when
Member member = memberService.findMember(id);
// then
Assertions.assertThat(member).isNotNull();
}
}
์๋ ์ฌ์ง๊ณผ ๊ฐ์ ๋ก๊ทธ๊ฐ ๋์จ๋ค
์ฌ์ค ๋งจ์ฒ์์๋ ์ ์๋ฌ์ฒ๋ฆฌ ๋ํ ๋์์ง ์๋ค๊ณ ์๊ฐํ๋ค
ํ์ง๋ง ๋ถ๋ช
ํ ๋ ์ข์ ๋ฐฉ๋ฒ์ด ์์ ๊ฑฐ๋ผ๊ณ ์๊ฐํ๊ณ , ๋ ์ข์ ๋ฐฉ๋ฒ์ ์๋์ ๊ฐ๋ค.
1-2) CustomException ์ฌ์ฉํ ์์ธ์ฒ๋ฆฌ + ๋ด์ฉ ์ถ๊ฐ
์ข๋ ๋ช ์์ ์ผ๋ก ์๋ฌ๋ฅผ ๊ด๋ฆฌํ๊ณ , ์๋ฌ ๊ด๋ จ ๋ด์ฉ์ ์ถ๊ฐํด๋ณด๊ฒ ๋ค.
public class MemberException extends RuntimeException{
public MemberException () {
}
public MemberException (String message) {
super(message);
}
public MemberException (String message, Throwable cause) {
super(message, cause);
}
public MemberException (Throwable cause) {
super(cause);
}
public MemberException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
Member ์ ๊ด๋ จ๋ Exception ์ ๋ค๋ฃจ๊ธฐ ์ํด MemberException ์ ๋ง๋ค๊ณ RuntimeException ์ ์์๋ฐ์๋ค
RuntimeException ์ ์ปดํ์ผ๋ฌ๊ฐ ์ดํ๋ฆฌ์ผ์ด์ ์คํ์ ์ฒดํฌํ์ง ์๋ ์์ธ(=UnCheckedException) ์ด๋ฏ๋ก, ์ดํ๋ฆฌ์ผ์ด์ ์ฌ์ฉ์ค์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
๊ทธ๋ฆฌ๊ณ RuntimeException ์์ฑ์๋ฅผ ๋ชจ๋ ๊ตฌํ๋ฐ๋๋ค
public Member findMember(Long id) {
Member member = memberRepository.findById(id)
.orElseThrow(() -> new MemberException(id + "์ ํด๋นํ๋ ํ์์ด ์กด์ฌ ํ์ง ์์ต๋๋ค."));
return member;
}
@Test
@DisplayName("Member ๋ฅผ ์กฐํํ๋ค.")
void findMemberTest() {
// given
Long id = 2839823L;
// when
memberService.findMember(id);
// then
//Assertions.assertThat(member).isNotNull();
}
๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ๋ค
์ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ฆฌ๋ ์ค ํ ์คํธ,์ด์ ํ๊ฒฝ์ ์ฌ๋ ธ์ ๋ ์๋ฌ ๋ฐ์์ ์๋์ ๊ฐ์ ์ฝ๋๊ฐ ๋ก๊ทธ๋ก ์ฐํ๊ฒ ๋๋ค
org.hyeonqz.dddstart.exception.MemberException: 2839823์ ํด๋นํ๋ ํ์์ด ์กด์ฌ ํ์ง ์์ต๋๋ค.
at org.hyeonqz.dddstart.application.MemberService.lambda$findMember$0(MemberService.java:18)
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
at org.hyeonqz.dddstart.application.MemberService.findMember(MemberService.java:18)
at org.hyeonqz.dddstart.application.MemberServiceTest.findMemberTest(MemberServiceTest.java:27)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
์ ์๋ฌ ๋ก๊ทธ๋ ํ
์คํธ ์ฝ๋์์ ๋์ค๋ ์๋ฌ ๋ก๊ทธ๋ผ ๋งจ์์ ์๋ฌ ๊ด๋ จ ๋ด์ฉ์ด 1์ค ๋ ์ถ๊ฐ๋์ด ์๋ค. ๋ฌด์ํด๋ ๋๋ค
1-3) CustomException + ErrorCode ์ฌ์ฉ
์์ ๋ฐฉ๋ฒ๋ 1๋ฒ ๋ฐฉ๋ฒ๋ณด๋ค๋ ๊ทธ๋๋ ๋ซ๋ค. Exception ์ด๋ฆ๋ ๋ช
์์ ์ผ๋ก ๋ฐ๋์๊ณ , ์๋ฌ ๋ด์ฉ ๋ํ ์ถ๊ฐ๋์ด ๋ก๊ทธ๋ฅผ ๋ณด๊ธฐ ๋ ์ฌ์์ก๋ค
์ฌ๊ธฐ์ ๋ ์ข์ ๋ฐฉ๋ฒ์ด ์์๊น?
์ฌ๋ฌ ๊ณ ๋ฏผ ๋์ ์๊ฐ๋ ๋ค์ ๋ฐฉ๋ฒ์ ์๋ฌ ์ํฉ์ ์๊ฐํด๋ณด๊ณ ์๋ฌ ์ฝ๋๋ฅผ ์ ์ํด๋๊ณ ์ฌ์ฉํ์์๋ค
๊ฐ๋ฐ์ ํ๋ค๋ณด๋ฉด id ๋ง ์กฐํํ์ง ์๊ณ ์ฌ๋ฌ ์๋ฌ ์ํฉ์ด ๋ถ๋ช
์๊ธด๋ค
์ด์ ๋ฐ๋ฅธ ErrorCode ๋ฅผ ์ ์ํด๋๊ณ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์๋์ ๊ฐ๋ค.
public interface ErrorCode {
String getCode();
String getDescription();
default String getMessage() {
return String.format("[%s] %s", getCode(), getDescription());
}
}
@RequiredArgsConstructor
public enum MemberErrorCode implements ErrorCode {
UNKNOWN_MEMBER_ID(INTERNAL_SERVER_ERROR,"ID ์ ํด๋นํ๋ ํ์์ด ์กด์ฌํ์ง ์์ต๋๋ค."),
BAD_CREDENTIALS(UNAUTHORIZED, "๊ณ์ ์ ๋ณด๊ฐ ์๋ชป๋์์ต๋๋ค."),
;
private final HttpStatus httpStatus;
private final String description;
@Override
public String getCode () {
return this.name();
}
}
public class MemberException extends RuntimeException{
private ErrorCode errorCode;
private Errors errors;
private Throwable errorCause;
public MemberException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public MemberException(ErrorCode errorCode, Errors errors) {
super(errorCode.getMessage());
this.errorCode = errorCode;
this.errors = errors;
}
public MemberException(ErrorCode errorCode, Throwable errorCause) {
super(errorCode.getMessage(), errorCause);
this.errorCode = errorCode;
this.errorCause = errorCause;
}
public MemberException(ErrorCode errorCode, Errors errors, Throwable errorCause) {
super(errorCode.getMessage(), errorCause);
this.errorCode = errorCode;
this.errors = errors;
this.errorCause = errorCause;
}
public MemberException(ErrorCode errorCode, String message) {
super(String.format("%s (Value: %s)", errorCode.getMessage(), message));
this.errorCode = errorCode;
}
}
public Member findMember(Long id) {
Member member = memberRepository.findById(id)
.orElseThrow(() -> new MemberException(MemberErrorCode.UNKNOWN_MEMBER_ID));
return member;
}
๊ณผ์ฐ ๊ฒฐ๊ณผ๋ ์ด๋ป๊ฒ ๋ฐ๋์์๊น?
๋ฏธ๋ฆฌ ์ ์ํด๋ ErrorCode ๋๋ก ์๋ฌ ๋ก๊ทธ๊ฐ ๋์๋ค. ์ข ๋ ๋ํ ์ผํ๊ฒ ํ๋ ค๋ฉด httpStatus ์ฝ๋๋ ์ถ๊ฐํ๊ณ ๋ง์๋๋ก Custom ํ ์๋์๋ค
๋ฏธ๋ฆฌ enum ์ ์๋ฅผ ํด๋ ์ผ๋ก์จ ์ข ๋ ์ ๋์ ์ผ๋ก ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๊ฒ ๋์๋ค
์ฒ์๋ณด๋ค ํจ์ฌ ๋ซ๋ค. ํ์ง๋ง ๋ญ๊ฐ ์ด์ง ์์ฝ๋ค. ์ด์ ์ํฉ์์ ์ ๊ฒ๋ณด๋ค ๊ธด ์๋ฌ ๋ก๊ทธ๋ค์ ํจ์ฌ ๋ง์ด ๋ณผ ์ ์๋ค
๊ทธ๋์ ๊ณ ๋ฏผ๋๋ ํฌ์ธํธ๊ฐ ํ๊ฐ ์๋ค
ํ์ฌ๋ ์์ธ ์ฒ๋ฆฌ๋ฅผ ํ๊ธด ํ์ง๋ง, ๊ฒฐ๊ตญ์ printStackTrace ์ ์ํด ๊ธด ์๋ฌ ๋ฉ์์ง๊ฐ ์ถ๋ ฅ์ด ๋๋ค
printStackTrace ์ฒ๋ผ ์๋ฌ ๋ก๊ทธ๋ฅผ ์ญ์ฑ ๋ณด์ฌ์ฃผ๋๊ฒ ๊ณผ์ฐ ์ข์๊ฑธ๊น? ๊ณผ์ฐ ๋ฌธ์ ๋ฅผ ๋น ๋ฅด๊ฒ ํด๊ฒฐํ๊ธฐ์ ์ข์๊น?
์๋๋ฉด ์๋ฌ ๋ก๊ทธ๋ฅผ ์งง๊ฒ ๋ณด์ฌ์ฃผ๋ฉด ๋ก๊ทธ๋ ์งง๊ฒ ๋ณด์ด์ง๋ง ๋ด๊ฐ ๋ฐ๋ก ์ฅ์ ์์น ๋ฐ ํด๊ฒฐ์ ๋น ๋ฅด๊ฒ ํ ์ ์์๊น?
์ ๋ถ๋ถ์์ ํญ์ ๋๋ ๋ง์ ๋น ์ง๊ณ ๊ณ ๋ฏผ์ ํ๊ฒ ๋๋ค
ํ์ฌ ์ค๋ฌด์์๋ printStackTrace() ๋ log.error ๋ฅผ ํต์ฉํ์ฌ ์ฌ์ฉํ๊ณ ์๊ธฐ๋ ํ๋ค
๊ทธ๋ฆฌ๊ณ logback-spring.xml ์ ์ ์ํ์ฌ ์ฌ์ฉํ๊ธฐ์ ํ ๋ฃฐ ์๋ฐ๋ผ ์ฒ๋ฆฌ ๋ฐฉ์์ด ๋ค๋ฅผ ๊ฒ์ด๋ค
๋ค์ ๋ฐฉ๋ฒ์ผ๋ก๋ printStackTrace() ๊ฐ ์๋ ๋ด๊ฐ ์ํ๋ ์๋ฌ๊ฐ๋ง ๋ฑ ๋ณผ ์ ์๊ฒ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณด์
1-4) ๋ก๊ทธ๋ฅผ ์ฌ์ฉํ ์งง์ ์๋ฌ์ฒ๋ฆฌ + GlobalExceptionHandler
๋ณดํต ์ ๋ฐฉ์์, ์ด์ ์ดํ๋ฆฌ์ผ์ด์ ๋ก๊ทธ์ ์ฐํ๋๊ฑธ ๋ชฉ์ ์ผ๋ก ํ๊ธฐ ๋ณด๋ค๋, ํด๋ผ์ด์ธํธ์ ์ด๋ ํ ์๋ต์ ์ค ๋๋ printStackTrace ๋ ํ์์๊ธฐ์ ํ์ค ์๋ต์ ์ฃผ๊ธฐ ์ํด ๋ง์ด ์ฌ์ฉํ๋ค.
์๋น์ค์์ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ์ง ์๊ณ GlobalException ์ผ๋ก ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์์ํ๋ค.
public Member findMember(Long id) {
Member member = memberRepository.findById(id)
.orElseThrow(() -> new MemberException(MemberErrorCode.UNKNOWN_MEMBER_ID));
return member;
}
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(MemberException.class)
public ResponseEntity<String> handleMemberException(MemberException ex) {
log.error("์์ธ ๋ฐ์: {}", ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ex.getMessage());
}
}
[UNKNOWN_MEMBER_ID] ID ์ ํด๋นํ๋ ํ์์ด ์กด์ฌํ์ง ์์ต๋๋ค. (Value: 2839823)
์๋ฌ ๋ฐ์์ ์์ฒ๋ผ 1์ค ๋ก๊ทธ๋ฅผ ๋ฝ์๋ผ ์ ์๋ค
๊ฒฐ๋ก
์์ธ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ๋ฌด์ํ๋ ๋ง๋ค
๊ทธ๋ฆฌ๊ณ ํ๋ง๋ค ๋ถ๋ช
๋ค๋ฅผ ๊ฒ์ด๋ค
๊ทธ๋ฌ๋ฏ๋ก ๋๋ ๋๋ง์ ์์น์ ์งํค๊ณ , ํ ๊ท์จ์ ๋ฐ๋ฅผ ์ ์๋๋ก ํด์ผํ ๊ฒ์ด๋ค
์ข ๋ ์ข์ ์๋ฌ์ฒ๋ฆฌ๋ฅผ ์๊ฐํด๋ณด๋ฉด ๊ฐ๋จํ ์๋ฌ์ผ ๊ฒฝ์ฐ ๋ก๊ทธ๋ก ์ฐ์ด์ ์ดํ๋ฆฌ์ผ์ด์
์๋ฒ ๋ก๊ทธ์ ๋ณด๊ดํ๊ณ
์ ๋ณด๊ฐ ๋ง์ด ํ์ํ ์๋ฌ ์ผ ๊ฒฝ์ฐ ๋ก๊ทธ๋ฅผ ๋ฐ๋ก ๊ด๋ฆฌํ ์ ์๋ค๋ฉด ์ ๋ง ์ข์ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ ๋ค
๊ทธ๋ฌ๊ธฐ ์ํด์๋ ELK,Kibana ๋ฑ ๋ก๊ทธ ๋ชจ๋ํฐ๋ง์ด ๋๋ฉด ์ข์ ๊ฒ์ด๋ค
ํ์ง๋ง ํ์ฌ ์ค๋ฌด๋ ๊ทธ๋ ์ง ์์ผ๋ ๋ญ ๋์ค์ ๋ด๊ฐ ๊ตฌ์ถ์ ํด๋ณด๋ ๊ฐ ํด์ผ๊ฒ ๋ค
๐ ์์ธ์ฒ๋ฆฌ์์ ๋๋ง์ ์์น
- ์์ธ์๋ ์ต๋ํ ๋ง์ ์ ๋ณด๋ฅผ ๋ฃ์.
- e.printStackTrace ๋ ํ์์ ์ํด์ ์ฌ์ฉํ๋ ์ต๋ํ ์ง์ํ์...
- ๊ฒฐ๊ตญ ํ ์คํธ, ์ด์์์ ๋ก๊ทธ๋ฅผ ๋ณด๋ฉฐ ์ฅ์ ๋ฅผ ์ฒ๋ฆฌํด์ผํ๊ธฐ ๋๋ฌธ์ ๋ก๊ทธ๋ง ๋ดค์ ๋ ๋ฌธ์ ๋ฅผ ์ ์ถํ๊ณ ํด๊ฒฐํ ์ ์๊ฒ๋ ์ ์ ํ๊ณ ๋ง์ ์ ๋ณด๋ฅผ ๋ฃ์๊ฐ ๋ชจํ ์ด๋ค.
Code GitHub : https://github.com/Hyeonqz/HyeonKyu-Lab/tree/master/spring-lab/src/main/java/org/hyeonqz/springlab/exceptionEx
HyeonKyu-Lab/spring-lab/src/main/java/org/hyeonqz/springlab/exceptionEx at master · Hyeonqz/HyeonKyu-Lab
ํ์ ๊ถ๊ธํ๋ ๋ด์ฉ์ ์คํํด๋ณด๋ ์ฐ๊ตฌ์(Java). Contribute to Hyeonqz/HyeonKyu-Lab development by creating an account on GitHub.
github.com