java version
openjdk 17.0.14 2025-01-21 LTS
OpenJDK Runtime Environment Zulu17.56+15-CA (build 17.0.14+7-LTS)
OpenJDK 64-Bit Server VM Zulu17.56+15-CA (build 17.0.14+7-LTS, mixed mode, sharing)
프로젝트 생성
스프링 부트 스타터 (https://start.spring.io/) 접속
Project: Gradle - Groovy
Language: Java
Spring Boot: 3.5.5
Project Metadata
Group: jpabook
Artifact: jpashop
packagin: Jar
Java: 17
Dependencies
Spring Web : Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.
Thymleaf : A modern server-side Java template engine for both web and standalone environments. Allows HTML to be correctly displayed in browsers and as static prototypes.
Spring Data JPA : Persist data in SQL stores with Java Persistence API using Spring Data and Hibernate.
H2 Database : Provides a fast in-memory database that supports JDBC API and R2DBC access, with a small (2mb) footprint. Supports embedded and server modes as well as a browser based console application.
Lombok : Java annotation library which helps to reduce boilerplate code.
Validation : Bean Validation with Hibernate validator.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.5'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'jpabook'
version = '0.0.1-SNAPSHOT'
description = 'Demo project for Spring Boot'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
Lombok 설정
Settings > plugin > lombok 검색 > 실행 (재시작)
Preferences > Annotation > annotation 검색 > Enable annotation processing 체크 (재시작)
Lombok 제대로 동작하는지 확인
Lombok은 Getter, Setter를 비롯한 반복적인 보일러플레이트 코드를 자동으로 생성해주는 라이브러리다.
임의의 테스트 클래스를 만들고 @Getter, @Setter가 동작하는지 확인해본다.main/java/jpabook/jpashop/Hello
package jpabook.jpashop;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class Hello {
private String data;
}
main/.../JpashopApplication
@SpringBootApplication
public class JpashopApplication {
public static void main(String[] args) {
Hello hello = new Hello();
hello.setData("hello");
String data = hello.getData();
System.out.println("data: " + data);
SpringApplication.run(JpashopApplication.class, args);
}
}
hello가 출력되면 성공이다.
라이브러리 살펴보기
오른쪽 사이드바의 Gradle 또는 터미널에서 확인 가능하다.
./gradlew dependencies --configuration compileClasspath
스프링부트 라이브러리
spring-boot-starter-webㄴ 웹 애플리케이션 개발을 위한 스타터, REST API와 MVC 개발 모두 가능
ㄴ spring-boot-starter-tomcat: 내장 톰캣 서버, 애플리케이션 실행 시 웹 서버 역할
ㄴ spring-webmvc: 스프링 MVC 프레임워크, 컨트롤러와 뷰를 연결해 요청-응답 처리
spring-boot-starter-thymeleafㄴ 타임리프 템플릿 엔진, 서버에서 HTML 뷰를 렌더링할 때 사용
spring-boot-starter-data-jpaㄴ JPA를 쉽게 사용할 수 있도록 지원하는 스타터
ㄴ spring-boot-starter-jdbc: JDBC로 데이터베이스와 연결
ㄴ `HikariCP 커넥션 풀`: 스프링 부트 2.0부터 기본, DB 연결 관리 성능 최적화
ㄴ hibernate + JPA: JPA의 구현체인 하이버네이트 + JPA API 제공
ㄴ spring-data-jpa: 스프링 데이터 JPA, Repository 인터페이스 자동 구현으로 JPA 사용 단순화
spring-boot-starter-aopㄴ 관점 지향 프로그래밍(AOP) 지원, 공통 로직(로깅, 보안, 트랜잭션 등)을 모듈화
spring-boot-starter (공통)
ㄴ 모든 스타터의 기본, 스프링 부트 핵심 기능과 로깅 포함
ㄴ spring-boot: 자동 설정, 실행 환경 제공
ㄴ `spring-core`: 스프링 핵심 기능, DI(의존성 주입), IoC(제어 역전) 등 지원
ㄴ spring-boot-starter-logging: 로깅 기능 지원
ㄴ `logback, slf4j`: 기본 로깅 구현체, 로그 출력 담당
테스트 라이브러리
spring-boot-starter-test
ㄴ 테스트를 위한 스타터, 다양한 테스트 라이브러리 포함
ㄴ junit: 대표적인 단위 테스트 프레임워크
ㄴ mockito: 가짜 객체(Mock) 생성해 단위 테스트 용이하게 지원
ㄴ assertj: 가독성 좋은 테스트 코드 작성을 돕는 라이브러리
ㄴ spring-test: 스프링 통합 테스트 지원 (ApplicationContext, MockMvc 등 제공)
핵심 라이브러리
스프링 MVC
ㄴ 웹 애플리케이션의 요청과 응답을 처리하는 스프링의 웹 프레임워크
스프링 ORM
ㄴ ORM(Object Relational Mapping) 지원 모듈, JPA나 하이버네이트와 연동
JPA, 하이버네이트
ㄴ JPA: 자바 ORM 표준 인터페이스
ㄴ 하이버네이트: JPA 구현체로 가장 많이 사용됨
스프링 데이터 JPA
ㄴ JPA를 더 편리하게 사용할 수 있도록 Repository 자동 구현 등 제공
기타 라이브러리
H2 데이터베이스
ㄴ 가볍고 인메모리 기반의 관계형 데이터베이스, 테스트 환경에서 주로 사용
클라이언트 커넥션 풀
ㄴ DB 연결을 관리하는 풀, 스프링 부트 기본은 HikariCP
WEB(thymeleaf)
ㄴ 서버에서 HTML 템플릿을 동적으로 렌더링해 뷰로 반환
로깅
ㄴ SLF4J & LogBack: 로그 추상화와 구현체, 시스템 상태 기록
테스트
ㄴ 다양한 테스트 도구 제공, 단위 테스트와 통합 테스트 모두 지원
View 환경 설정
thymeleaf 템플릿 엔진
thymeleaf 공식 사이트: https://www.thymeleaf.org/
템플릿 엔진은 HTML 같은 뷰 템플릿에 데이터를 주입하여 동적으로 화면을 생성하는 도구이다. 템플릿 엔진에는 Thymeleaf, Apache Freemarker, Mustache, Groovy Templates 등이 있다.
이 중 타임리프(Thymeleaf)는 스프링 부트에서 가장 널리 사용되는 템플릿 엔진으로, HTML 마크업을 깨뜨리지 않고 그대로 사용할 수 있는 Natural Templates라는 특징을 가지고 있다. JSP나 Freemarker는 웹 브라우저에서 직접 열어볼 수 없지만, 타임리프는 HTML 파일을 그대로 열면 정상적으로 표시되기 때문에 개발 과정에서 편리하다. 또한 서버사이드 렌더링을 지원하여 서버에서 데이터를 반영한 HTML을 생성할 수 있고, 스프링과의 통합이 뛰어나 폼 처리나 국제화(i18n) 기능을 자연스럽게 활용할 수 있다.
✔️ 스프링부트 thymeleaf viewName 매핑resources/templates/+{ViewName}+.html
실습
main/…/HelloController
package jpabook.jpashop;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
// 이 클래스를 스프링 MVC의 컨트롤러로 등록한다.
// (스프링 빈으로 등록되며, 웹 요청을 처리할 수 있다)
@Controller
public class HelloController {
// /hello 경로로 GET 요청이 들어오면 이 메서드가 실행된다.
@GetMapping("hello")
public String hello(Model model) {
// Model 객체에 데이터를 담아 뷰(template)로 전달할 수 있다.
model.addAttribute("data", "hello!!!");
// 뷰 리졸버(ViewResolver)가 resources/templates/hello.html을 찾아서 렌더링한다.
return "hello";
}
}
resources/templates/hello.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p th:text="'안녕하세요. ' + ${data}" >안녕하세요. 손님</p> </body>
</html>
localhost:8080/hello 에 접속하면 서버사이드 렌더링되어 안녕하세요. hello!!! 가 표시되어야 한다.
정적인 페이지 (순수 html)
resources/tatic/index.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Hello</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
Hello
<a href="/hello">hello</a>
</body>
</html>
서버 재시작 없이 View 파일 변경하기
build.gradle의 dependecies에 spring-boot-devtools 추가 후 재실행
dependencies {
...
implementation 'org.springframework.boot:spring-boot-devtools'
...
}
로그에 [ restartedMain]이 뜨면 devtools가 알맞게 세팅된 것이다.
2025-09-07T20:45:58.129+09:00 INFO 51957 --- [jpashop] [ restartedMain] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-09-07T20:45:58.131+09:00 INFO 51957 --- [jpashop] [ restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-09-07T20:45:58.147+09:00 WARN 51957 --- [jpashop] [ restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
변경사항이 있을 때마다 html 파일 → build → Recompile 을 눌러 서버 재시작 없이 변경사항을 확인한다.
H2 데이터 베이스
H2 데이터베이스는 개발이나 테스트 용도로 가볍고 편리하게 사용할 수 있는 인메모리 데이터베이스다. H2는 웹 콘솔을 제공하여 브라우저에서 데이터베이스를 조회하고 관리할 수 있다.
다운로드
https://www.h2database.com
Version 2.3.232 (2024-08-11) > All Platforms (zip, 9.5 MB)
로컬에서 압축을 풀고 h2/bin 폴더로 이동한 후 실행할 수 있다.
> SPRING-BOOT-1 % cd h2/bin
> bin % ls
h2-2.3.232.jar h2.bat h2.sh h2w.bat
> bin % chmod +x ./h2.sh
> bin % ./h2.sh
h2.sh를 실행하면 자동으로 브라우저에서 H2 콘솔 페이지가 열린다. JDBC URL에는 DB 파일을 생성할 경로를 지정해야 한다. 경로를 지정한 뒤 연결 버튼을 누르면 파일 모드로 데이터베이스에 접근할 수 있다.
지정한 경로에 jpashop.mv.db 파일이 생성된 것을 확인한 후, 좌측 최상단에 있는 연결 끊기 버튼을 눌러 연결을 종료한다. 그 다음 H2 JDBC URL에 jdbc:h2:tcp://localhost/~/jpashop를 입력하고 연결을 누르면 네트워크 모드로 접근된다. 이때 로컬에서 DB 서버가 실행되고, 클라이언트 애플리케이션은 jdbc:h2:tcp://localhost/... URL을 통해 서버에 접속해 데이터를 읽고 쓸 수 있다.
이제 H2에서 SQL 쿼리를 작성하고, 테이블을 생성·수정하며 데이터를 조회하고 관리할 수 있어, 스프링부트 애플리케이션과 연동해 개발 및 테스트 환경을 구축할 수 있다.
JPA와 DB 설정, 동작 확인
먼저 resources/application.properties 파일을 삭제하고 application.yml 파일을 생성한다. 설정 항목이 복잡하고 많아질수록 yml 형식이 가독성이 좋아 관리하기 편리하다.
resources/application.yml
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/jpashop
username: sa
password:
driver-class-name: org.h2.Driver # H2 데이터베이스에 연결하기 위한 JDBC 드라이버 클래스
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
show_sql: true
format_sql: true
logging.level:
org.hibernate.SQL: debug
org.hibernate.orm.jdbc.bind: trace
이제 H2 데이터베이스와 JPA가 연동되어 애플리케이션 실행 시 테이블이 자동 생성되고, 실행되는 SQL을 로그로 확인할 수 있다.
driver-class-name: org.h2.Driver
H2 데이터베이스에 연결하기 위해 사용할 JDBC 드라이버 클래스를 지정하는 설정이다. 이 값이 지정되면 스프링부트는 url, username, password와 함께 DataSource를 자동으로 생성한다. 내부적으로는 기본 커넥션 풀 구현체인 HikariCP를 사용해 커넥션 풀을 구성하고, 애플리케이션은 이 풀을 통해 안정적으로 데이터베이스와 연결을 주고받을 수 있다.
⊕ JDBC(Java Database Connectivity)
자바에서 데이터베이스에 접근하기 위해 제공하는 표준 API(라이브러리)로, 자바 프로그램과 데이터베이스 사이를 연결해주는 다리 역할을 한다.개발자는 SQL을 작성하고 JDBC API를 통해 실행하면, 그 결과가 자바 객체 형태로 반환된다. 데이터베이스 종류(H2, MySQL, Oracle 등)에 상관없이 JDBC 인터페이스는 동일하다. 대신 DB마다 JDBC 드라이버라는 구현체가 존재해, 각 데이터베이스와 실제 통신을 처리한다.
⊕ 커넥션 풀 (Connection Pool)
데이터베이스와 연결을 효율적으로 관리하기 위한 연결 모음집이다. 일반적으로 애플리케이션이 DB에 접근하려면 DB 커넥션을 새로 열고 닫는 과정이 필요하다. 그런데 이 과정은 네트워크와 인증 절차 때문에 비용이 크고 느리다. 매번 새로 연결을 만들면 성능이 크게 떨어진다.그래서 커넥션 풀은 미리 일정 개수의 DB 커넥션을 만들어 두고, 애플리케이션이 필요할 때 빌려 쓰고, 사용을 마치면 다시 반환해서 재사용한다. 덕분에 불필요한 커넥션 생성·종료 비용을 줄이고, 안정적으로 빠른 DB 접근이 가능해진다. Spring Boot는 기본적으로 HikariCP라는 커넥션 풀 라이브러리를 사용해서, 우리가 별도로 설정하지 않아도 자동으로 풀을 구성해준다.
동작 확인
main/…/Member : 회원 엔티티
회원 정보를 DB 테이블과 매핑한 자바 클래스
package jpabook.jpashop;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
}
main/…/MemberRepository : 회원 리포지토리
회원 엔티티에 대한 DB 조회·저장·삭제 등의 기능을 제공하는 인터페이스
회원 데이터를 찾아주고, 저장·수정·삭제를 처리한다.
@Repository
public class MemberRepository {
@PersistenceContext
private EntityManager em;
public Long save(Member member) {
em.persist(member);
return member.getId();
}
public Member find(Long id) {
return em.find(Member.class, id);
}
}
@Repository 를 통해 스프링이 자동으로 컴포넌트 스캔하여 Bean으로 등록한다.
@PersistenceContext는 엔티티 매니저(EntityManager)를 주입받기 위한 애너테이션이다. 스프링부트를 사용하면 모든 것이 스프링 컨테이너 위에서 관리되므로, @PersistenceContext가 붙은 필드에 자동으로 EntityManager가 주입된다. EntityManager는 JPA에서 엔티티를 DB에 저장, 조회, 수정, 삭제하는 핵심 역할을 수행하며, 스프링부트에서는 spring-boot-starter-data-jpa가 application.yml 또는 application.properties 설정을 기반으로 자동으로 생성하고 관리한다.
테스트
MemberRepository에서 ctrl + Enter > Test 를 선택하여 Junit5 테스트 코드를 작성한다.
package jpabook.jpashop;
import jakarta.transaction.Transactional;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
@SpringBootTest
public class MemberRepositoryTest {
@Autowired // MemberRepository를 주입받는다.
MemberRepository memberRepository;
@Test
@Transactional
@Rollback(false)
public void testMember() throws Exception {
// given
Member member = new Member();
member.setUsername("memberA");
// when
Long savedId = memberRepository.save(member);
Member findMember = memberRepository.find(savedId);
// then
Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
Assertions.assertThat(findMember).isEqualTo(member);
System.out.println("findMember == member: " + (findMember == member)); // true
}
}
@Transactional 없이 테스트를 실행하면 오류가 발생한다. 그 이유는 엔티티 매니저(EntityManager)를 통한 모든 데이터 변경 작업은 반드시 트랜잭션 안에서 이루어져야 하기 때문이다. JPA는 트랜잭션 경계 안에서만 변경 내용을 DB에 반영할 수 있다.
@Rollback(false) : 테스트 코드에 @Transactional이 적용되어 있을 때, 기본적으로 DB 변경이 롤백되므로 이를 막고 실제로 데이터가 저장되도록 하기 위해 사용한다. 이를 통해 테스트 실행 후 DB에 데이터가 제대로 반영되었는지 직접 확인할 수 있다.
애플리케이션을 다시 실행하면, Hibernate가 엔티티를 기반으로 테이블을 생성하는 과정을 로그로 볼 수 있다.
테스트를 실행하면 h2 데이터베이스에 값이 업데이트된다.
2025-09-08T01:51:26.150+09:00 DEBUG 3656 --- [ Test worker] org.hibernate.SQL :
insert
into
member
(username, id)
values
(?, ?)
jar을 통한 동작 확인
터미널에서 프로젝트 루트 폴더로 이동한 뒤, ./gradlew clean build를 실행하면 이전 빌드 결과물이 삭제되고 새로 빌드가 수행되며, build/libs 폴더에 JAR 파일이 생성되는 것을 확인할 수 있다.
생성된 JAR 파일은 다음 명령어로 실행할 수 있다. 이 과정은 애플리케이션을 실제 환경에 배포할 때 사용된다.
java -jar build/libs/jpashop-0.0.1-SNAPSHOT.jar
쿼리 파라미터 로그 남기기
JPA를 사용하다 보면 SQL이 실제로 데이터베이스로 날아가는 시점과, 데이터베이스 커넥션을 가져오는 시점이 언제인지 궁금할 때가 많다. 그런데 기본 설정으로는 쿼리 파라미터(SQL 문에서 값이 동적으로 바뀌는 부분)를 로그에 찍을 수 없어 답답하다. 예를 들어 아래와 같이 로그를 확인하면, 실제 값 대신 물음표(?)로만 표시되어 어떤 값이 바인딩됐는지 알 수 없다.
2025-09-08T01:51:26.150+09:00 DEBUG 3656 --- [ Test worker] org.hibernate.SQL :
insert
into
member
(username, id)
values
(?, ?)
이 문제를 해결하기 위해, 실제 쿼리 파라미터까지 로그로 남겨주는 방법은 다음과 같다.
먼저 application.yml 파일에서 logging.level에 org.hibernate.type: trace를 추가하면, 어떤 값이 파라미터로 바인딩되었는지 아래와 같이 로그로 확인할 수 있다.
2025-09-08T01:55:46.889+09:00 TRACE 3721 --- [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [memberA]
2025-09-08T01:55:46.889+09:00 TRACE 3721 --- [ Test worker] org.hibernate.orm.jdbc.bind : binding parameter (2:BIGINT) <- [1]
더 자세한 쿼리 파라미터 로그를 원한다면 외부 라이브러리(https://github.com/gavlyukovskiy/spring-boot-data-source-decorator)를 사용할 수 있다. 라이브러리를 통해 데이터베이스 커넥션을 래핑하여 SQL 구문과 바인딩된 값을 이해하고 로그로 출력할 수 있다. 대표적인 라이브러리로는 P6SPY와 Datasource Proxy가 있으며, P6SPY를 사용하려면 build.gradle에 아래 의존성을 추가하면 된다.
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.10.0'
이렇게 설정하면 아래와 같이 쿼리 파라미터에 어떤 값이 바인딩되었는지 확인할 수 있다.
2025-09-08T02:25:03.747+09:00 INFO 4429 --- [ Test worker] p6spy : #1757265903747 | took 0ms | statement | connection 4| url jdbc:h2:tcp://localhost/~/jpashop
insert into member (username,id) values (?,?)
insert into member (username,id) values ('memberA',1);