*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성
- Windows11(윈도우 11) 환경
- 자바 JDK 17 버전 설치 https://yungenie.tistory.com/11
[Java] 차근차근 Java 설치하기 (JDK17, Window 11)
자바 개발 도구 설치 방법에 대해서 알아보겠습니다. Java17은 LTS(Long Term Support : 장기 지원) 릴리즈로 1년 후까지 기술 지원 및 버그를 개선한 서비스를 제공받을 수 있습니다. 업데이트 버전을 꾸
yungenie.tistory.com
- 스프링 부트 4.31.0 사용 - STS(Spring Tool Suite) 설치(Spring Tools for Eclipse - https://spring.io/tools)
=> https://priming.tistory.com/147 참고
[Windows] Spring Tool Suite 4(STS 4) 다운로드 및 설치
STS란?Spring Tool Suite(STS)는 스프링 프로젝트를 생성하고, 개발할 수 있게 해주는 도구입니다. STS 설치 과정에 대해 설명드리겠습니다. 설치 파일 다운로드STS 공식 사이트에서 설치 파일을 다운로
priming.tistory.com

- MySQL Community Server 8.0.42 설치 https://dev.mysql.com/downloads/mysql/
MySQL :: Download MySQL Community Server
Select Version: 9.3.0 Innovation 8.4.5 LTS 8.0.42 Select Operating System: Select Operating System… Microsoft Windows Ubuntu Linux Debian Linux SUSE Linux Enterprise Server Red Hat Enterprise Linux / Oracle Linux Fedora Linux - Generic Oracle Solaris mac
dev.mysql.com

- Gradle
**STS에서 Gradle 프로젝트 생성한 과정



*** 함께 보면 좋은 글
스프링 부트 핵심 가이드(장정우 지음) - 스프링 부트 개요
1. 스프링 프레임워크자바(Java) 기반 애플리케이션 프레임워크로, 엔터프라이즈급(기업 환경 대상 개발) 애플리케이션을 위한 다양한 기능 제공-> 오픈소스 경량급 애플리케이션 프레임워크로
keep-programming-study.tistory.com
스프링 부트 핵심 가이드(장정우 지음) - 개발에 앞서 알면 좋은 기초 지식
1. 서버 간 통신마이크로서비스 아키텍처에서 한 서버가 다른 서버에 통신을 요청하는 것을 의미-> 한 대는 서버/다른 한 대는 클라이언트가 됨 가장 많이 사용되는 방식은 HTTP/HTTPS 방식(TCP/IP, SOA
keep-programming-study.tistory.com
- 애플리케이션을 개발할 때는 불가피하게 많은 오류가 발생하게 됨
- 자바에서는 try/catch, throw 구문을 활용해 예외 처리
- 스프링 부트에서는 자바보다 더욱 편리하게 예외 처리를 할 수 있는 기능 제공
1. 예외와 에러
1) 예외(exception)
- 입력 값 처리가 불가능하거나 참조된 값이 잘못된 경우 등, 애플리케이션이 정상적으로 동작하지 못하는 상황
- 개발자가 미리 코드 설계를 통해 직접 처리할 수 있음
2) 에러(error)
- 메모리 부족(OutOfMemory)과 스택 오버플로우(StackOverFlow) 등,
미리 애플리케이션의 코드를 살펴보며 문제가 발생하지 않도록 예방해야 하는 문제 - 자바의 가상머신에서 발생시키는 것으로, 애플리케이션 코드에서 처리할 수 있는 것이 거의 없음
2. 예외 클래스

- 자바의 모든 예외 클래스는 Throwable 클래스를 상속받음
- 자바의 예외 클래스 중 Exception 클래스는 다양한 자식 클래스를 갖고 있으며,
Cheched Exception과 Unchecked Exception으로 구분할 수 있음
| Checked Exception | Unchecked Exception | |
| 예외 처리 여부 | 반드시 예외 처리 필요 | 명시적으로 예외 처리를 강제하지 않음 |
| 확인 가능 시점 | 컴파일 단계 | 실행 중 단계 |
| 대표적인 예외 클래스 | IOException SQLException |
RuntimeException NullPointerException IllegalArgumentException IndexOutOfBoundException SystemException |
| RuntimeException 상속 여부 | 상속받지 않음 | 상속받음 |
3. 예외 처리 방법
1) 예외 복구
- 예외 상황을 파악하여 문제를 해결하는 방식
- try/catch 구문이 대표적인 방법:
try 블록에는 예외가 발생할 수 있는 코드를 작성하고, catch 블록(여러 개 작성 가능)에는 try 블록에서 발생하는 예외 상황을 처리하는 내용 작성
-> 예외 상황이 발생하면 애플리케이션에서 여러 개의 catch 블록을 순차적으로 거치며,
예외 유형과 매칭되는 블록을 찾아 예외 처리 동작 수행
int a = 1;
String b = "a";
try {
System.out.println(a + Integer.parseInt(b));
} catch (NumberFormatException e) {
b = "2";
System.out.println(a + Integer.parseInt(b));
}
2) 예외 처리 회피
- 예외가 발생한 시점에서 바로 처리하는 것이 아니라, 예외가 발생한 메서드를 호출한 곳에서 에러 처리할 수 있게 전가
- throw 키워드로 어떤 예외가 발생했는지 호출부에 내용을 전달
int a = 1;
String b = "a";
try {
System.out.println(a + Integer.parseInt(b));
} catch (NumberFormatException e) {
throw new NumberFormatException("숫자가 아닙니다.");
}
3) 예외 전환
- 앞의 두 방식을 적절하게 섞은 것으로, 커스텀 예외를 만드는 과정에서 사용
- try/catch 방식을 사용하면서 catch 블록에서 throw 키워드로 다른 예외 타입으로 전달
4. 스프링 부트의 예외 처리 방식
- 클라이언트에게 어떤 문제가 발생했는지 상황을 전달하는 방식
-> 각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달 - @(Rest)ControllerAdvice와 @ExceptionHandler 어노테이션을 통해 모든 컨트롤러의 예외를 처리하거나,
@ExceptionHandler를 통해 특정 컨트롤러의 예외만 처리
1) 예외 타입 레벨에 따른 예외 처리 우선순위
- @ControllerAdvice 클래스 내에 동일하게 핸들러 메서드가 선언된 상태에서
Exception 클래스와 그보다 좀 더 구체적인 NullPointerException.class)가 각각 선언된 경우에는,
구체적인 클래스가 지정된 쪽이 우선순위를 가짐

2) 핸들러 위치에 따른 예외 처리 우선순위
- @ControllerAdvice의 글로벌 예외 처리와 @Controller 내의 컨트롤러 예외 처리에 동일한 타입의 예외 처리를 하면,
범위가 좁은 컨트롤러의 핸들러 메서드가 우선순위를 갖게 됨

5. 커스텀 예외 클래스 생성 및 활용
- 표준 예외에서 제공하는 클래스는 해당 예외 타입의 이름만으로 이해하기 어려운 경우가 많기에,
사용 시 반드시 예외 메시지를 상세히 작성해야 함 - 표준 예외를 상속받은 커스텀 예외를 사용하면,
애플리케이션에서 발생하는 예외를 개발자가 직접 코드로 관리하기 때문에 책임 소재를 애플리케이션으로 가져와,
동일한 예외 상황이 발생할 경우 한 곳에서 처리하며 특정 상황에 맞는 예외 코드를 적용할 수 있음 - 이 예제에서는 클래스의 구조적인 설계를 통한 예외 클래스 생성 방법을 알아보는 것이 목적이므로, 클래스명도 Exception으로 함
*커스텀 예외 클래스 구조

1) Throwable을 상속받는 Exception 클래스 생성
package com.example.demo;
// 사용자 정의 Exception 클래스 (주의: java.lang.Exception과 이름이 같아서 혼동될 수 있음)
public class Exception extends Throwable {
// 직렬화(serialization)를 위한 고유 ID 값
// 객체를 직렬화/역직렬화할 때 클래스 버전이 맞는지 확인하는 용도
static final long serialVersionUID = -3387516993124229948L;
// 기본 생성자: 메시지나 원인 없이 예외 객체 생성
public Exception() {
super(); // 부모 클래스(Throwable)의 기본 생성자 호출
}
// 메시지를 받아서 예외 객체 생성
public Exception(String message) {
super(message); // 부모 클래스에 메시지 전달
}
// 메시지와 원인(cause)을 함께 받아서 예외 객체 생성
public Exception(String message, Throwable cause) {
super(message, cause); // 부모 클래스에 메시지와 원인 전달
}
// 원인(cause)만 받아서 예외 객체 생성
public Exception(Throwable cause) {
super(cause); // 부모 클래스에 원인 전달
}
// 고급 생성자: 메시지, 원인, suppression 여부, stack trace 기록 여부까지 설정 가능
// suppression: 예외를 억제할 수 있는지 여부
// writableStackTrace: 스택 트레이스를 기록할지 여부
protected Exception(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
2) Constants 클래스 생성 - 도메인 레벨 표현을 위한 열거형을 생성
package com.example.demo;
public class Constants {
// enum(열거형) 정의: 예외 클래스 이름을 관리하기 위한 열거형
public enum ExceptionClass {
// 열거형 상수 선언: PRODUCT 라는 예외 클래스 타입을 정의
// 괄호 안의 "Product"는 생성자에 전달되는 값
PRODUCT("Product");
// 열거형이 내부적으로 가질 문자열 값
private String exceptionClass;
// enum 생성자: 각 상수에 문자열 값을 연결
// 예: PRODUCT → "Product"
ExceptionClass(String exceptionClass) {
this.exceptionClass = exceptionClass;
}
// getter 메서드: enum에 저장된 문자열 값을 반환
public String getExceptionClass() {
return exceptionClass;
}
// toString() 메서드 오버라이드: enum을 문자열로 표현할 때의 형식 지정
// 예: PRODUCT → "ProductException."
@Override
public String toString() {
return getExceptionClass() + "Exception.";
}
}
}
3) 커스텀 예외 클래스 생성
package com.example.demo;
//Spring Framework에서 제공하는 HttpStatus 열거형을 사용하기 위해 import
//(예: 404 NOT_FOUND, 500 INTERNAL_SERVER_ERROR 등)
import org.springframework.http.HttpStatus;
public class CustomException extends Exception {
// Constants 클래스 안에 정의된 ExceptionClass enum을 참조
// 예외의 종류(예: ProductException)를 나타냄
private Constants.ExceptionClass exceptionClass;
// HTTP 상태 코드 (예: 404, 500 등)를 저장
private HttpStatus httpStatus;
// 생성자: 예외 클래스, HTTP 상태, 메시지를 받아서 예외 객체 생성
public CustomException(Constants.ExceptionClass exceptionClass, HttpStatus httpStatus, String message) {
// 부모 클래스(Exception)의 생성자를 호출하면서
// "ProductException. + message" 형태로 메시지를 전달
super(exceptionClass.toString() + message);
// 전달받은 예외 클래스와 HTTP 상태를 필드에 저장
this.exceptionClass = exceptionClass;
this.httpStatus = httpStatus;
}
// 예외 클래스(enum)를 반환하는 getter
public Constants.ExceptionClass getExceptionClass() {
return exceptionClass;
}
// HTTP 상태 코드 숫자값을 반환 (예: 404, 500)
public int getHttpStatusCode() {
return httpStatus.value();
}
// HTTP 상태 코드의 설명 문자열을 반환 (예: "Not Found", "Internal Server Error")
public String getHttpStatusType() {
return httpStatus.getReasonPhrase();
}
// HttpStatus 객체 자체를 반환하는 getter
public HttpStatus getHttpStatus() {
return httpStatus;
}
}
4) GlobalExceptionHandler 클래스에 CustomException에 대한 예외 처리 코드 추가
package com.example.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 애플리케이션 전역에서 발생하는 예외를 처리하는 클래스
* 특히 유효성 검사 실패(MethodArgumentNotValidException)에 대한 응답을 커스터마이징하여
* 클라이언트에게 명확한 오류 메시지를 전달합
*/
@RestControllerAdvice // 모든 컨트롤러에 적용되는 전역 예외 처리기
public class GlobalExceptionHandler {
// 로거(Logger) 선언: 로그를 남기기 위해 SLF4J Logger 사용
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
... 생략 ...
/**
* CustomException을 처리
* @param e CustomException: 사용자 정의 예외
* @param request HttpServletRequest: 요청 정보(URI 등)를 가져오기 위해 사용
* @return ResponseEntity<Map<String, String>>: 예외 정보를 담은 JSON 응답
*/
@ExceptionHandler(value=CustomException.class)
public ResponseEntity<Map<String, String>> handleException(CustomException e, HttpServletRequest request) {
// 응답 헤더 객체 생성
HttpHeaders responseHeaders = new HttpHeaders();
// 예외 발생 시 로그 기록 (URI와 메시지 출력)
LOGGER.error("Advice 내 handleException 호출, {}, {}", request.getRequestURI(), e.getMessage());
// 응답 본문에 담을 Map 생성
Map<String, String> map = new HashMap<>();
map.put("error type", e.getHttpStatusType()); // 예외 타입 (예: "Bad Request")
map.put("code", Integer.toString(e.getHttpStatusCode())); // HTTP 상태 코드 숫자 (예: 400)
map.put("message", e.getMessage()); // 예외 메시지
// ResponseEntity로 JSON 응답 반환 (헤더 + 본문 + 상태코드)
return new ResponseEntity<>(map, responseHeaders, e.getHttpStatus());
}
}
5) CustomException을 발생시키는 컨트롤러 메서드 추가
package com.example.demo.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.Constants.ExceptionClass;
import com.example.demo.CustomException;
/**
* 예외 발생을 테스트하기 위한 컨트롤러 클래스
* - @RestController: REST API 응답을 반환하는 컨트롤러임을 나타냄
*/
@RestController
public class ExceptionController {
/**
* /custom 엔드포인트를 GET 요청으로 호출했을 때 실행되는 메서드
* - 항상 CustomException을 강제로 발생시킴
* - 예외는 GlobalExceptionHandler에서 처리되어 클라이언트에게 JSON 응답으로 반환됨
*
* @throws CustomException 사용자 정의 예외
*/
@GetMapping("/custom") // GET 요청을 "/custom" 경로에 매핑
public void getCustomException() throws CustomException {
// CustomException을 강제로 발생시킴
// ExceptionClass.PRODUCT → "ProductException."
// HttpStatus.BAD_REQUEST → 400 상태 코드
// 메시지 → "getCustomException 메서드 호출"
throw new CustomException(ExceptionClass.PRODUCT, HttpStatus.BAD_REQUEST, "getCustomException 메서드 호출");
}
}
6) http://localhost:8080/swagger-ui/index.html에 접속하여 결과 확인
- 서버 재시작 후 엄청난 오류가 발생하여 다음 글에서 해결하겠습니다...
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2025-11-26 16:06:50.724 [main] ERROR o.s.boot.SpringApplication - Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1826)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:607)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:373)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:970)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350)
at com.example.demo.StudyApplication.main(StudyApplication.java:11)
Caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:276)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
at org.hibernate.boot.model.relational.Database.<init>(Database.java:45)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:226)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:194)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:171)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1442)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1513)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:66)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:419)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:400)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1873)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1822)
... 15 common frames omitted
Caused by: org.hibernate.HibernateException: Unable to determine Dialect without JDBC metadata (please set 'jakarta.persistence.jdbc.url' for common cases or 'hibernate.dialect' when a custom Dialect implementation must be provided)
at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.determineDialect(DialectFactoryImpl.java:191)
at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.buildDialect(DialectFactoryImpl.java:87)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentWithDefaults(JdbcEnvironmentInitiator.java:186)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:408)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:129)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:81)
at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
... 30 common frames omitted
'스프링(Spring), 스프링부트(SpringBoot) > 스프링부트(SpringBoot) 기초' 카테고리의 다른 글
| 스프링부트 핵심 가이드(장정우 지음) - 액추에이터 주요 기능 살펴보기, 액추에이터 커스텀 기능 만들기 (0) | 2026.02.19 |
|---|---|
| 스프링부트 핵심 가이드(장정우 지음) - 예외 처리 에러 해결, 액추에이터 활용하기 (0) | 2026.02.04 |
| 스프링부트 핵심 가이드(장정우 지음) - 유효성 검사(2) (0) | 2025.11.10 |
| 스프링부트 핵심 가이드(장정우 지음) - 유효성 검사(1) (0) | 2025.10.24 |
| 스프링 부트 핵심 가이드(장정우 지음) - 연관관계 매핑 in JPA 마지막[영속성 전이(Cascade)] (0) | 2025.10.20 |