정화 코딩
[SpringBoot] 졸업 프로젝트 PreView: 프로젝트 초기 세팅 2 본문
Database 연결

MySQL Workbench에서 preview라는 이름의 스키마를 새롭게 만들어준다.

지난 포스팅에서 spring initializer를 통해서 프로젝트 초기 세팅을 했었는데, 그걸로 만들면 기본적으로 application.properties 파일로 만들어진다.

나는 application.properties 파일을 삭제하고 application.yml 파일을 새로 생성해주었다.
spring:
datasource:
url: jdbc:mysql://localhost:3307/preview
username: [데이터베이스 유저네임]
password: [데이터베이스 비밀번호]
driver-class-name: com.mysql.cj.jdbc.Driver
이렇게 적어주면 된다.

실행을 시켜보면 어플리케이션도 잘 실행되고 데이터베이스에도 잘 연결된 것을 확인할 수 있다.
global 파일 생성

위와 같이 디렉토리 구조를 만들었다. domain 폴더 안에 도메인 별로 폴더를 만들면 되고, 이번 글에서는 global 폴더 안에 들어갈 공통 파일들을 작성해보려고 한다.
ErrorStatus.java
package com.chapssal_tteok.preview.global.apiPayload.code.status;
import com.chapssal_tteok.preview.global.apiPayload.code.BaseErrorCode;
import com.chapssal_tteok.preview.global.apiPayload.code.ErrorReasonDTO;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
@AllArgsConstructor
public enum ErrorStatus implements BaseErrorCode {
// 가장 일반적인 응답
_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."),
_BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."),
_UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."),
_FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."),
_NOT_FOUND(HttpStatus.NOT_FOUND, "COMMON404", "찾을 수 없습니다.");
private final HttpStatus httpStatus;
private final String code;
private final String message;
@Override
public ErrorReasonDTO getReason() {
return ErrorReasonDTO.builder()
.message(message)
.code(code)
.isSuccess(false)
.build();
}
@Override
public ErrorReasonDTO getReasonHttpStatus() {
return ErrorReasonDTO.builder()
.message(message)
.code(code)
.isSuccess(false)
.httpStatus(httpStatus)
.build();
}
}
SuccessStatus.java
package com.chapssal_tteok.preview.global.apiPayload.code.status;
import com.chapssal_tteok.preview.global.apiPayload.code.BaseCode;
import com.chapssal_tteok.preview.global.apiPayload.code.ReasonDTO;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
@AllArgsConstructor
public enum SuccessStatus implements BaseCode {
// 일반적인 응답
_OK(HttpStatus.OK, "COMMON200", "성공입니다.");
private final HttpStatus httpStatus;
private final String code;
private final String message;
@Override
public ReasonDTO getReason() {
return ReasonDTO.builder()
.message(message)
.code(code)
.isSuccess(true)
.build();
}
@Override
public ReasonDTO getReasonHttpStatus() {
return ReasonDTO.builder()
.message(message)
.code(code)
.isSuccess(true)
.httpStatus(httpStatus)
.build();
}
}
BaseCode.java
package com.chapssal_tteok.preview.global.apiPayload.code;
public interface BaseCode {
ReasonDTO getReason();
ReasonDTO getReasonHttpStatus();
}
BaseErrorCode.java
package com.chapssal_tteok.preview.global.apiPayload.code;
public interface BaseErrorCode {
ErrorReasonDTO getReason();
ErrorReasonDTO getReasonHttpStatus();
}
ErrorReasonDTO.java
package com.chapssal_tteok.preview.global.apiPayload.code;
import lombok.Builder;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
@Builder
public class ErrorReasonDTO {
private HttpStatus httpStatus;
private final boolean isSuccess;
private final String code;
private final String message;
public boolean getIsSuccess() { return isSuccess; }
}
ReasonDTO.java
package com.chapssal_tteok.preview.global.apiPayload.code;
import lombok.Builder;
import lombok.Getter;
import org.springframework.http.HttpStatus;
@Getter
@Builder
public class ReasonDTO {
private HttpStatus httpStatus;
private final boolean isSuccess;
private final String code;
private final String message;
public boolean getIsSuccess() { return isSuccess; }
}
ExceptionAdvice.java
package com.chapssal_tteok.preview.global.apiPayload.exception;
import com.chapssal_tteok.preview.global.apiPayload.ApiResponse;
import com.chapssal_tteok.preview.global.apiPayload.code.ErrorReasonDTO;
import com.chapssal_tteok.preview.global.apiPayload.code.status.ErrorStatus;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
@Slf4j
@RestControllerAdvice(annotations = {RestController.class})
public class ExceptionAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler
public ResponseEntity<Object> validation(ConstraintViolationException e, WebRequest request) {
String errorMessage = e.getConstraintViolations().stream()
.map(constraintViolation -> constraintViolation.getMessage())
.findFirst()
.orElseThrow(() -> new RuntimeException("ConstraintViolationException 추출 도중 에러 발생"));
return handleExceptionInternalConstraint(e, ErrorStatus.valueOf(errorMessage), HttpHeaders.EMPTY,request);
}
@Override
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
Map<String, String> errors = new LinkedHashMap<>();
e.getBindingResult().getFieldErrors().stream()
.forEach(fieldError -> {
String fieldName = fieldError.getField();
String errorMessage = Optional.ofNullable(fieldError.getDefaultMessage()).orElse("");
errors.merge(fieldName, errorMessage, (existingErrorMessage, newErrorMessage) -> existingErrorMessage + ", " + newErrorMessage);
});
return handleExceptionInternalArgs(e,HttpHeaders.EMPTY, ErrorStatus.valueOf("_BAD_REQUEST"),request,errors);
}
@ExceptionHandler
public ResponseEntity<Object> exception(Exception e, WebRequest request) {
e.printStackTrace();
return handleExceptionInternalFalse(e, ErrorStatus._INTERNAL_SERVER_ERROR, HttpHeaders.EMPTY, ErrorStatus._INTERNAL_SERVER_ERROR.getHttpStatus(),request, e.getMessage());
}
@ExceptionHandler(value = GeneralException.class)
public ResponseEntity onThrowException(GeneralException generalException, HttpServletRequest request) {
ErrorReasonDTO errorReasonHttpStatus = generalException.getErrorReasonHttpStatus();
return handleExceptionInternal(generalException,errorReasonHttpStatus,null,request);
}
private ResponseEntity<Object> handleExceptionInternal (Exception e, ErrorReasonDTO reason,
HttpHeaders headers, HttpServletRequest request) {
ApiResponse<Object> body = ApiResponse.onFailure(reason.getCode(), reason.getMessage(), null);
// e.printStackTrace();
WebRequest webRequest = new ServletWebRequest(request);
return super.handleExceptionInternal(
e,
body,
headers,
reason.getHttpStatus(),
webRequest
);
}
private ResponseEntity<Object> handleExceptionInternalFalse(Exception e, ErrorStatus errorCommonStatus,
HttpHeaders headers, HttpStatus status, WebRequest request, String errorPoint) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorPoint);
return super.handleExceptionInternal(
e,
body,
headers,
status,
request
);
}
private ResponseEntity<Object> handleExceptionInternalArgs(Exception e, HttpHeaders headers, ErrorStatus errorCommonStatus,
WebRequest request, Map<String, String> errorArgs) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(),errorCommonStatus.getMessage(),errorArgs);
return super.handleExceptionInternal(
e,
body,
headers,
errorCommonStatus.getHttpStatus(),
request
);
}
private ResponseEntity<Object> handleExceptionInternalConstraint(Exception e, ErrorStatus errorCommonStatus,
HttpHeaders headers, WebRequest request) {
ApiResponse<Object> body = ApiResponse.onFailure(errorCommonStatus.getCode(), errorCommonStatus.getMessage(), null);
return super.handleExceptionInternal(
e,
body,
headers,
errorCommonStatus.getHttpStatus(),
request
);
}
}
GeneralException.java
package com.chapssal_tteok.preview.global.apiPayload.exception;
import com.chapssal_tteok.preview.global.apiPayload.code.BaseErrorCode;
import com.chapssal_tteok.preview.global.apiPayload.code.ErrorReasonDTO;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class GeneralException extends RuntimeException {
private BaseErrorCode code;
public ErrorReasonDTO getErrorReason() {
return this.code.getReason();
}
public ErrorReasonDTO getErrorReasonHttpStatus(){
return this.code.getReasonHttpStatus();
}
}
ApiResponse.java
package com.chapssal_tteok.preview.global.apiPayload;
import com.chapssal_tteok.preview.global.apiPayload.code.BaseCode;
import com.chapssal_tteok.preview.global.apiPayload.code.status.SuccessStatus;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
public class ApiResponse<T> {
@JsonProperty("isSuccess")
private final Boolean isSuccess;
private final String code;
private final String message;
@JsonInclude(JsonInclude.Include.NON_NULL)
private T result;
// 성공한 경우 응답 생성
public static <T> ApiResponse<T> onSuccess(T result){
return new ApiResponse<>(true, SuccessStatus._OK.getCode() , SuccessStatus._OK.getMessage(), result);
}
public static <T> ApiResponse<T> of(BaseCode code, T result){
return new ApiResponse<>(true, code.getReasonHttpStatus().getCode() , code.getReasonHttpStatus().getMessage(), result);
}
// 실패한 경우 응답 생성
public static <T> ApiResponse<T> onFailure(String code, String message, T data){
return new ApiResponse<>(false, code, message, data);
}
}
build.gradle
...
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
providedCompile 'jakarta.platform:jakarta.jakartaee-web-api:8.0.0'
implementation 'org.springframework.boot:spring-boot-starter-security'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
...
PreviewApplication.java
package com.chapssal_tteok.preview;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing
public class PreviewApplication {
public static void main(String[] args) {
SpringApplication.run(PreviewApplication.class, args);
}
}
@EnableJpaAuditing가 있어야 @CreatedDate, @LastModifiedDate가 동작함.
ExceptionAdvice.java 파일을 작성하는 과정에서 오류가 발생했는데, 그건 이 글에서 확인할 수 있다.
'Web Development' 카테고리의 다른 글
[Back-end] free-tier AWS EC2 인스턴스 생성 후 서버 실행 (0) | 2025.04.04 |
---|---|
[Back-end] free-tier AWS RDS (MySQL) 인스턴스 생성 (0) | 2025.04.03 |
[SpringBoot] jakarta.validation 관련 Cannot resolve symbol 'validation' 오류 발생 원인과 해결 방법 (1) | 2025.03.26 |
[SpringBoot] 졸업 프로젝트 PreView: DB ERD 설계 (0) | 2025.03.19 |
[SpringBoot] 졸업 프로젝트 PreView: 프로젝트 초기 세팅 (0) | 2025.03.17 |