[Java] Spring Boot: 방명록 Rest API 구현(feat. MySQL 연동)
이전 내용
[Java] Spring Boot: 코드, 요청&응답 과정 이해하기
이전 내용 [Java] Spring Boot: Maven Repository이전 내용 [Java] 스프링부트: Spring Initializr - Dependencies이전 내용 [Java] Spring Boot: 제어 역전, 의존성 주입이전 내용 [Java] Spring Boot - 기본 구조, "Hello World" 띄워
puppy-foot-it.tistory.com
Rest API
◆ REST API(Representational State Transfer API)
REST API는 HTTP 프로토콜을 기반으로 하는 API로, 웹 서비스를 설계하기 위한 아키텍처 스타일로, 클라이언트와 서버 간의 상호작용을 효율적으로 관리한다.

[REST 와 API]
- REST: REST는 자원(리소스)의 상태 표현을 사용하여 클라이언트와 서버 간의 통신을 정의하는 아키텍처 스타일이다. 각 리소스는 URI(Uniform Resource Identifier)를 통해 고유하게 식별된다.
- API: 응용 프로그램 프로그래밍 인터페이스(Application Programming Interface)로, 소프트웨어 간의 통신을 위한 규약을 의미한다.
[REST의 특징]
- 무상태성(Stateless): 서버는 클라이언트의 상태를 저장하고, 각 요청은 독립적이며 필요한 모든 정보를 포함해야 한다.
- 캐시 가능(Cacheable): 클라이언트와 서버 간의 데이터 전송 비용을 줄이기 위해 응답이 캐시될 수 있다. 이는 성능을 향상시킨다.
- 계층화: REST 아키텍처는 여러 계층으로 구성될 수 있으며, 클라이언트는 최상위 레벨의 서버와 통신하지만, 실제 리소스는 여러 중간 서버를 통해 제공될 수 있다.
- 일관된 인터페이스: 모든 요청은 특정 HTTP 메서드(예: GET, POST, PUT, DELETE)를 사용하여 수행된다.
[HTTP 메서드와 REST API]
- GET: 리소스 조회. 서버에서 데이터를 요청할 때 사용.
- POST: 새로운 리소스 생성. 서버에 새 데이터를 전송할 때 사용.
- PUT: 기존 리소스 업데이트. 특정 리소스를 완전히 교체할 때 사용.
- PATCH: 기존 리소스를 부분적으로 업데이트. 필요한 수정만 반영.
- DELETE: 특정 리소스 삭제. 서버에서 해당 리소스를 제거.
[REST API 설계 원칙]
- 자원기반: URI는 리소스를 표현해야 하며, 명사 형식으로 설계.
- /users: 사용자 목록을 조회
- /users/1: 특정 사용자의 상세 정보를 조회
- HTTP 상태 코드 사용: 요청의 성공 여부를 나타내기 위해 적절한 HTTP 상태 코드 사용.
- 200 OK: 요청 성공
- 201 Created: 리소스 생성 성공
- 204 No Content: 삭제 성공
- 404 Not Found: 요청한 리소스가 없음
- 500 Internal Server Error: 서버 오류
[간단한 REST API 예시]
간단한 사용자 관리 REST API의 예.
- GET /users: 모든 사용자 목록 조회
- GET /users/{id}: 특정 사용자 정보 조회
- POST /users: 새로운 사용자 추가
- PUT /users/{id}: 특정 사용자 정보 업데이트
- DELETE /users/{id}: 특정 사용자 삭제
방명록 Rest API 구현하기
(feat.MySQL 연동)
다음의 요구사항에 알맞은 방명록 REST API를 구현하라.
1. 방명록 테이블은 아래와 같은 구조로 설계
- id: Primary Key (자동 증가)
- author: 작성자 이름
- content: 방명록 내용
- createdAt: 작성 시간
- updatedAt: 수정 시간
이를 SQL 로 작성하면 아래와 같다.
CREATE TABLE guestbook (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
author VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP
);
2. 해당 데이터를 로컬 프로그램에 설치된 MySQL과 연동되도록 하라.
단, MySQL에 접속해서 해당 테이블을 직접 생성, 삽입, 수정, 삭제 하지말고 스프링 부트에서 모든 작업이 진행될 수 있도록 할 것.
3. API 엔드포인트
HTTP Method | 엔드포인트 | 설명 |
POST | /api/guestbook | 방명록 생성 |
GET | /api/guestbook | 모든 방명록 조회 |
GET | /api/guestbook{id} | 특정 방명록 조회 |
PUT | /api/guestbook{id} | 특정 방명록 수정 |
DELETE | /api/guestbook{id} | 특정 방명록 삭제 |
풀이

[순서 요약 1] ※ 세부 과정
의존성 관리 → properties 설정 추가 → 엔터티 구성(Article.java) → 리포지토리(인터페이스) 생성 → 서비스 클래스 생성 및 메소드 구현 → 컨트롤러 생성 및 메소드 구현 → API 테스트 → 태스트 코드 작성
[순서 요약 2] ※ 전체 과정
방명록 개발을 위한 엔티티 구성 → 방명록 작성을 위한 API 구현 → 방명록 목록 조회를 위한 API 구현 → 방명록 삭제 API 구현 → 방명록 수정 API 구현 (내용 상 한 번에 다 소개)
1. 의존성 관리
Initializr 에서 필요한 모듈을 dependencies를 통해 넣어준다. 특히, 로컬 컴퓨터에 설치된 MySQL을 사용할 것이므로, mysql-connector-j를 넣어줘야 한다. (또는, build.gradle에서 넣어줘도 된다.)
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-web'
implementation 'org.springframework.boot:spring-boot-starter-mustache'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'com.mysql:mysql-connector-j'
}
상단의 내용은 build.gradle의 내용 중 dependencies 부분이다.
2. properties 추가해주기
그리고나서 src - main - resources 의 applcation.properties 로 들어와서 포트를 변경하고 (8080 포트와 충돌을 피하기 위함)
mysql을 사용하기 위한 설정을 추가해 준다.
spring.application.name=guestBook
server.port = 8081
spring.datasource.url=jdbc:mysql://localhost:3306/tabledb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=사용자명
spring.datasource.password=비밀번호
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
- spring.datasource.username=사용자명:
이 항목은 데이터베이스에 연결하기 위해 사용할 사용자 계정 설정. 실제 사용자명으로 변경 요망. - spring.datasource.password=비밀번호:
데이터베이스 사용자 계정의 비밀번호를 설정하는 항목. 실제 비밀번호로 변경하여 입력 요망. - spring.datasource.driver-class-name=cohttp://m.mysql.cj.jdbc.Driver:
MySQL 데이터베이스를 사용하기 위해 필요한 JDBC 드라이버 클래스 지정. cohttp://m.mysql.cj.jdbc.Driver는 MySQL Connector/J 8.0 이상 버전에 사용되는 드라이버 클래스.
JPA 설정
- spring.jpa.show-sql=true:
애플리케이션이 실행될 때 Hibernate가 실행하는 SQL 쿼리를 콘솔에 출력하도록 설정. 이를 통해 데이터베이스와의 상호작용을 쉽게 디버깅할 수 있다. - spring.jpa.hibernate.ddl-auto=update:
Hibernate의 DDL(데이터 정의 언어) 자동 생성 방법을 설정. update 값은 애플리케이션이 실행될 때 데이터베이스 스키마를 자동으로 업데이트. 즉, 기존의 데이터베이스 구조를 변경하거나 새로운 엔티티가 추가될 경우 이를 반영. - spring.jpa.properties.hibernate.format_sql=true:
출력되는 SQL 쿼리가 보기 쉽게 포맷되어 출력되도록 설정. 이를 통해 SQL 쿼리를 보다 쉽게 이해하고 확인할 수 있다.
[hibernate.ddl-auto의 다른 설정 값]
- none: DDL 자동 생성이 이루어지지 않는다.
- validate: 엔티티와 데이터베이스 스키마를 비교하여 문제가 있을 경우 오류를 발생시킨다.
- create: 매번 애플리케이션이 시작될 때마다 기존 데이터를 삭제하고 새로운 데이터베이스 스키마를 생성한다.
- create-drop: create와 동일하지만 애플리케이션 종료 시 데이터베이스를 삭제한다.
3. 엔티티 구성하기 (JPA)
domain 패키지에 Article.java 파일을 만든 다음 해당 코드 입력
package com.example.guestBook.domain;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity // 엔티티 지정
@Table(name = "guestbook")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder // 빌터 패턴으로 객체 생성
@EntityListeners(AuditingEntityListener.class) // 최초 입력 시간, 수정 시간 자동 관리 위함
public class Article {
@Id // id 필드ㅣ 기본키
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 자동으로 1씩 증가
private Long id;
@Column(nullable = false, length=50) // 작성자, Not Null
private String author;
@Column(nullable = false, columnDefinition = "TEXT") // 내용, Not Null
private String content;
@CreatedDate // 작성 시간
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate // 수정 시간
@Column(nullable = true)
private LocalDateTime updatedAt;
public Article(String author, String content) {
this.author = author;
this.content = content;
}
@PrePersist // 엔티티가 데이터베이스에 저장되기 전에 createdAT을 현재 시간으로 설정
protected void onCreate() {
this.createdAt = LocalDateTime.now();
}
@PreUpdate // 엔티티가 업데이트할 때 updatedAT을 현재 시간으로 설정
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
// 엔티티에 요청받은 내용으로 값 수정 메소드
public void update(String author, String content) {
this.author = author;
this.content = content;
}
}
[주요 애너테이션]
- @Entity: 이 클래스가 JPA의 엔티티임을 나타냄.
- @Table(name = "guestbook"): 이 엔티티가 guestbook이라는 테이블과 매핑된다는 것을 나타냄.
- @EntityListeners(AuditingEntityListener.class): 기록의 생성 및 수정 시간을 자동으로 관리하도록 지정.
- @Getter: Lombok 라이브러리의 애너테이션으로, 클래스의 모든 필드에 대한 getter 메서드를 자동으로 생성.
- @Setter: Lombok 라이브러리의 애너테이션으로, 클래스의 모든 필드에 대한 setter 메서드를 자동으로 생성.
- @NoArgsConstructor: Lombok 애너테이션으로, 매개변수가 없는 기본 생성자를 자동으로 생성.
- @AllArgsConstructor: Lombok 애너테이션으로, 모든 필드를 인자로 받는 생성자를 자동으로 생성.
- @Builder: Lombok 애너테이션으로, 빌더 패턴을 사용하여 객체를 생성할 수 있는 메서드를 자동으로 생성.
- @EntityListeners(AuditingEntityListener.class): 이 엔티티의 생명주기 이벤트에 대해 지정된 리스너(여기서는 AuditingEntityListener)를 등록. 이를 통해 자동으로 생성 시간과 수정 시간을 관리.
※ 빌더 패턴(Builder Pattern): 복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴. 생성자에 들어갈 매개 변수를 메서드로 하나하나 받아들이고 마지막에 통합 빌드해서 객체를 생성하는 방식
[필드 설명]
- ID 필드:
- @Id: 기본 키를 지정. (Primary Key를 가지는 변수 선언)
- @GeneratedValue(strategy = GenerationType.IDENTITY): 기본 키 값을 증가시키도록 설정.
- 작성자 필드:
- @Column(nullable = false, length=50): 작성자의 이름을 저장하며, NULL이 허용되지 않고 최대 길이는 50자.
- 내용 필드:
- @Column(nullable = false, columnDefinition = "TEXT"): 내용이 저장되며, NULL이 허용되지 않고 긴 텍스트 형식으로 저장.
- 작성 시간:
- @CreatedDate: 글이 작성된 시간을 자동으로 관리.
- @Column(nullable = false, updatable = false): NULL이 허용되지 않으며, 수정 시 업데이트되지 않음.
- 수정 시간:
- @LastModifiedDate: 글이 수정된 시간을 기록.
- @Column(nullable = true): NULL 허용.
- 메서드 설명
- 생성자: 기본 생성자와 작성자, 내용을 인자로 받는 생성자가 제공 됨.
- @PrePersist / @PreUpdate:
- onCreate(): 엔티티가 데이터베이스에 저장되기 전에 생성 시간을 현재 시간으로 설정.
- onUpdate(): 엔티티가 업데이트될 때 수정 시간을 현재 시간으로 설정.
- update(): 작성자와 내용을 수정하는 메서드. 이 메서드를 통해 기존 엔티티의 데이터를 갱신할 수 있다.
4. 리포지토리 생성
repository 패키지를 생성하고, GuestBookRepository.java 파일을 생성 후 GuestBookRepository 인터페이스 생성
package com.example.guestBook.repository;
import com.example.guestBook.domain.Article;
import org.springframework.data.jpa.repository.JpaRepository;
public interface GuestBookRepository extends JpaRepository<Article, Long> {
}
- JpaRepository: Spring Data JPA에서 제공하는 인터페이스 중 하나로, JPA를 사용하여 데이터베이스를 조작하기 위한 메서드들을 제공
- JPARepository 인터페이스를 상속받는 인터페이스를 정의하면, 해당 인터페이스를 구현하는 클래스는 JPA에서 제공하는 메서드 사용 가능
- JpaRepository를 사용하면, 복잡한 JDBC(Java DataBase Connectivity) 코드를 작성하지 않아도 간단하게 DB와의 데이터 접근 작업 처리 가능
- JPARepository 인터페이스는 제네릭 타입을 사용하여 Entity클래스와 복합키를 사용하고 있다면 해당 Entity의 ID클래스를 명시 ▶ JpaRepository<T, ID> *T: Entity 클래스
[JPA 메소드]
메소드 | 기능 |
save() | 레코드 저장 (insert, update) |
findOne() | primary key로 레코드 한건 찾기 |
findAll() | 전체 레코드 불러오기. 정렬(sort), 페이징(pageable) 가능 |
count() | 레코드 갯수 세기 |
delete() | 레코드 삭제 |
5. 방명록 작성, 조회, 수정, 삭제 등의 기능을 위한 API 구현
5-1. dto 패키지 생성 및 코드 작성
먼저 dto 패키지를 생성하고, dto 패키지를 서비스 계층에서 요청을 받을 객체인 AddArticleRequest 객체 생성
※ DTO는 단순하게 데이터를 옮기기 위한 전달자 역할을 하므로, 별도의 비즈니스 로직을 포함하지 않음.
package com.example.guestBook.dto;
import com.example.guestBook.domain.Article;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자
@Getter
public class AddArticleRequest {
private String author;
private String content;
public Article toEntity() {
return Article.builder()
.author(author)
.content(content)
.build();
}
}
- toEntity(): 빌더 패턴을 사용해 DTO를 엔터티로 만들어주는 메소드. 추후 방명록에 글 추가 시 저장할 엔터티로 변환하는 용도로 사용.
- Article.builder(): Article 클래스에 정의된 정적 메소드를 호출하여, 빌더 객체를 생성.
- 각각 Article 객체의 author와 content 속성을 설정. author와 content는 이 메소드 내에서 정의된 변수들로, Article 객체의 속성을 나타냄.
- .build() 메소드를 호출하여 설정된 값을 기반으로 Article 객체를 생성
5-2. 서비스 메소드 코드 작성
service 패키지를 생성한 뒤, GuestBookService.java를 생성 후 GuestBookService 클래스 생성하고, 방명록(Guestbook)과 관련된 다양한 기능을 제공하는 메소드 작성
package com.example.guestBook.service;
import com.example.guestBook.domain.Article;
import com.example.guestBook.dto.AddArticleRequest;
import com.example.guestBook.dto.UpdateArticleRequest;
import com.example.guestBook.repository.GuestBookRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@RequiredArgsConstructor // final이 붙거나 @NotNull 붙은 필드의 생성자 추가
@Service // 빈으로 등록
public class GuestBookService {
private final GuestBookRepository guestBookRepository;
// 블로그 글 추가 메소드
public Article save(AddArticleRequest request) {
return guestBookRepository.save(request.toEntity());
}
public List<Article> findAll() {
return guestBookRepository.findAll();
}
// 방명록 하나 조회하는 메소드 추가
public Article findById(Long id) {
return guestBookRepository.findById(id).
orElseThrow(() -> new IllegalArgumentException("not found: " + id));
}
// 방명록의 ID 받은 뒤 db에서 삭제
public void delete(Long id) {
guestBookRepository.deleteById(id);
}
// 방명록 수정하는 메소드
@Transactional
public Article update(Long id, UpdateArticleRequest request) {
Article article = guestBookRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("not found: " + id));
article.update(request.getAuthor(), request.getContent());
return article;
}
}
- @RequestArgsConstructor: 빈을 생성자로 생성하는 롬복에서 지원하는 애너테이션으로, final 키워드나 @NotNull이 붙은 필드로 생성자를 만들어 줌.
- @ Service: 해당 클래스를 빈으로 서블릿 컨테이너에 등록해줌.
- save(): JpaRepository에서 지원하는 저장 메서드. AddArtcicleRequest 클래스에 저장된 값들을 article 데이터베이스에 저장.
- findAll(): 데이터베이스에 저장된 모든 방명록 글을 조회하여 반환
- findById(): 주어진 ID로 방명록 글을 조회하며, 해당 ID의 글이 존재하지 않을 경우 IllegalArgumentException을 발생
- delete(): 방명록 글의 ID를 받은 뒤 JPA에서 제공하는 deleteById() 메소드를 이용해 데이터베이스에서 데이터 삭제
- update(): 주어진 ID의 방명록 글을 조회한 후 존재하지 않으면 예외를 발생시킨다. 그 후, 요청에서 전달받은 데이터로 글을 수정하고 해당 객체를 반환.
- @Transactional 애너테이션: 이 메소드가 데이터베이스 트랜잭션을 지원하도록 함.
※ IllegalArgumentException: 오류 발생 지점에서 잘못 된 파라미터(인자)가 넘어갔을 때 발생하는 오류
5-3. 컨트롤러 메소드 코드 작성
URL에 매핑하기 위한 컨트롤러 메소드 추가.
컨트롤러 메소드에는 URL 매핑 애너테이션을 사용할 수 있다.
[주요 URL 매핑 애너테이션]
- @GetMapping : HTTP 메소드가 GET일 때 요청받은 URL과 동일한 메서드와 매핑 ▶ 조회
- @PostMapping: HTTP 메소드가 POST일 때 요청받은 URL과 동일한 메서드와 매핑 ▶ 입력
- @PutMapping : HTTP 메소드가 PUT일 때 요청받은 URL과 동일한 메서드와 매핑 ▶ 수정
- @DeleteMapping : HTTP 메소드가 DELETE일 때 요청받은 URL과 동일한 메서드와 매핑 ▶ 삭제
controller 패키지를 생성한 뒤, controller 패키지에서 GuestBookApiController.java 파일을 생성 후 컨트롤러 메소드 작성
package com.example.guestBook.controller;
import com.example.guestBook.domain.Article;
import com.example.guestBook.dto.AddArticleRequest;
import com.example.guestBook.dto.ArticleResponse;
import com.example.guestBook.dto.UpdateArticleRequest;
import com.example.guestBook.service.GuestBookService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RequiredArgsConstructor
@RestController// HTTP Response Body에 객체 데이터를 JSON 형식으로 반환하는 컨트롤러
public class GuestBookApiController {
private final GuestBookService guestBookService;
// HTTP 메서드가 POST일 때 전달받은 URL 동일하면 메소드로 매핑
@PostMapping("/api/guestbook")
// 요청 본문 값 매핑
public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request) {
Article savedArticle = guestBookService.save(request);
// 요청 자원이 성공적으로 생성되었으며 저장된 블로그 글 정보를 응답 객체에 담아 전송
return ResponseEntity.status(HttpStatus.CREATED)
.body(savedArticle);
}
// 방명록 글 조회 후 반환
@GetMapping("/api/guestbook")
public ResponseEntity<List<ArticleResponse>> findAllArticles() {
List<ArticleResponse> articles = guestBookService.findAll().stream()
.map(ArticleResponse::new)
.toList();
return ResponseEntity.ok()
.body(articles);
}
// GET 요청올 때 방명록 조회하기 위해 매핑할 메소드 작성
@GetMapping("/api/guestbook/{id}")
public ResponseEntity<ArticleResponse> findById(@PathVariable Long id) {
Article article = guestBookService.findById(id);
return ResponseEntity.ok()
.body(new ArticleResponse(article));
}
// DELETE 요청이 오면 글 삭제 위한 메소드 작성
@DeleteMapping("/api/guestbook/{id}")
public ResponseEntity<Void> deleteArticle(@PathVariable Long id) {
guestBookService.deleteArticle(id);
return ResponseEntity.ok().build();
}
// PUT 요청이 오면 글을 수정하기 위한 UpdateArticle() 메소드
@PutMapping("/api/guestbook/{id}")
public ResponseEntity<Article> updateArticle(@PathVariable Long id, @RequestBody UpdateArticleRequest request) {
Article updatedArticle = guestBookService.update(id, request);
return ResponseEntity.ok()
.body(updatedArticle);
}
}
- @RestController 애너테이션: HTTP 응답으로 객체 데이터를 JSON 형식으로 반환
- @RequestBody 애너테이션: HTTP를 요청할 때 응답에 해당하는 값을 @RequestBody 애너테이션이 붙은 대상 객체인 AddArticleRequest에 매핑
- @ResponseEntity.status().body(): 응답 코드로 201 (Created)를 응답하고 테이블에 저장된 객체 반환
6. 테스트 (포스트맨)
스프링 부트 서버(GuestBookApplication.java)를 실행한 후, 포스트맨을 켜서 기능이 정상적으로 작동되는지 확인한다.
package com.example.guestBook;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing // 수정된 날짜가 자동으로 업데이트하도록 설정
public class GuestBookApplication {
public static void main(String[] args) {
SpringApplication.run(GuestBookApplication.class, args);
}
}
[테이블 생성]
서버를 실행한 뒤, MySQL Workbench를 실행하여 테이블이 잘 실행되었는지 확인

guestbook 테이블이 잘 생성되었고, SQL 문으로 데이터를 조회해보면 데이터가 잘 뜨는 것을 확인할 수 있다.
SELECT * FROM guestbook;
[조회]
HTTP 메소드를 GET으로 선택하고, url에 엔드포인트를 입력한 뒤 탭에서 Params를 선택하고 SEND를 누르면

데이터가 조회되는 것을 확인할 수 있다.
[특정 방명록 조회]
포스트맨에서 HTTP 메소드를 GET으로 설정하고, 엔드포인트를 입력(api/guestbook/특정id)하고 나서 Params에 두고 SEND 클릭

ID 5번의 방명록이 잘 조회되는 것을 확인할 수 있다.
[데이터 생성]
포스트맨에서 HTTP 메소드를 POST로 설정하고, 엔드포인트를 입력한 뒤(URL), Body - Raw - JSON으로 설정 후, 새로운 값을 입력하여 SEND를 누르면

새로운 값이 잘 저장되는 것을 확인할 수 있다.
[데이터 삭제]
방금 생성한 id 7의 방명록을 삭제하려면
HTTP 메소드: DELETE, 엔드포인트는 api/guestbook/7 로 지정하고, Params로 설정한 뒤 SEND를 누르면

방명록이 잘 삭제된 것을 확인할 수 있다.
[데이터 수정]
데이터 수정을 위해 방명록을 하나 생성한다 (엔드포인트는 api/guestbook)
id가 8로 생성된 것을 확인할 수 있는데,

수정을 위해 엔드포인트는 api/guestbook/8로 지정하고, HTTP 메소드는 PUT으로, Body - raw - JSON 으로 바꾼 뒤, 변경할 값을 입력해주면

작성자가 홍길순에서 홍길동으로 변경된 것을 확인할 수 있다.
이것으로 방명록 테이블의 생성, 데이터 삽입, 조회, 삭제, 수정 기능이 잘 구현된 것을 확인할 수 있다.
7. 테스트 코드 작성
보통 코드를 작성한 후, 해당 코드에 이상이 없는지 테스트 작업을 반복적으로 수행해야 하는데, 이를 줄여줄 테스트 코드를 작성한다.
GuestBookApiController 클래스에 alt+enter를 누르고 [테스트 생성]을 클릭하여 테스트 코드 파일을 생성한다.
이 파일은 /test/java/패키지 아래에 생성되는데, 이 파일의 코드를 입력해 준다.
이 테스트는 방명록 기능에 대한 다양한 시나리오를 검증하며, 각 테스트 메소드는 방명록의 CRUD(Create, Read, Update, Delete) 기능을 확인한다.
package com.example.guestBook.controller;
import com.example.guestBook.domain.Article;
import com.example.guestBook.dto.AddArticleRequest;
import com.example.guestBook.dto.UpdateArticleRequest;
import com.example.guestBook.repository.GuestBookRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@SpringBootTest // 테스트용 애플리케이션 컨텍스트
@AutoConfigureMockMvc // MockMvc 생성
class GuestBookApiControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper;
@Autowired
private WebApplicationContext context;
@Autowired
GuestBookRepository guestBookRepository;
@BeforeEach // 테스트 실행 전 실행 메소드
public void mockMvcSetup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
guestBookRepository.deleteAll();
}
@DisplayName("addArticle: 방명록 추가 성공")
@Test
public void addArticle() throws Exception {
//given
final String url = "/api/guestbook";
final String author = "author";
final String content = "content";
final AddArticleRequest userRequest = new AddArticleRequest(author, content);
// 객체 JSON으로 직렬화
final String requestBody = objectMapper.writeValueAsString(userRequest);
// when
// 설정한 내용 바탕으로 요청 전송
ResultActions result = mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(requestBody));
//then
result.andExpect(status().isCreated());
List<Article> articles = guestBookRepository.findAll();
assertThat(articles.size()).isEqualTo(1); // 크기가 1인지 검증
assertThat(articles.get(0).getAuthor()).isEqualTo(author);
assertThat(articles.get(0).getContent()).isEqualTo(content);
}
@DisplayName("findAllArticles: 방명록 글 목록 조회 성공")
@Test
public void findAllArticles() throws Exception {
//given
final String url = "/api/guestbook";
final String author = "author";
final String content = "content";
guestBookRepository.save(Article.builder()
.author(author)
.content(content)
.build());
//when
final ResultActions resultActions = mockMvc.perform(get(url)
.accept(MediaType.APPLICATION_JSON_VALUE));
//then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].content").value(content))
.andExpect(jsonPath("$[0].author").value(author));
}
@DisplayName("findArticle: 방명록 글 조회 성공")
@Test
public void findArticle() throws Exception {
//given
final String url = "/api/guestbook/{id}";
final String author = "author";
final String content = "content";
Article savedArticle = guestBookRepository.save(Article.builder()
.author(author)
.content(content)
.build());
// when
final ResultActions resultActions = mockMvc.perform(get(url, savedArticle.getId()));
// then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").value(content))
.andExpect(jsonPath("$.author").value(author));
}
@DisplayName("deleteArticle: 방명록 글 삭제 성공")
@Test
public void deleteArticle() throws Exception {
//given
final String url = "/api/guestbook/{id}";
final String author = "author";
final String content = "content";
Article savedArticle = guestBookRepository.save(Article.builder()
.author(author)
.content(content)
.build());
//when
mockMvc.perform(delete(url, savedArticle.getId()))
.andExpect(status().isOk());
//then
List<Article> articles = guestBookRepository.findAll();
assertThat(articles).isEmpty();
}
@DisplayName("updateArticle: 블로그 글 수정 성공")
@Test
public void updateArticle() throws Exception {
//given
final String url = "/api/guestbook/{id}";
final String author = "author";
final String content = "content";
Article savedArticle = guestBookRepository.save(Article.builder()
.author(author)
.content(content)
.build());
final String newAuthor = "new Author";
final String newContent = "new Content";
UpdateArticleRequest request = new UpdateArticleRequest(newAuthor, newContent);
// when
ResultActions result = mockMvc.perform(put(url, savedArticle.getId())
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(request)));
//then
result.andExpect(status().isOk());
Article article = guestBookRepository.findById(savedArticle.getId()).get();
assertThat(article.getAuthor()).isEqualTo(newAuthor);
assertThat(article.getContent()).isEqualTo(newContent);
}
}
- 애너테이션
- @SpringBootTest: Spring Boot 애플리케이션의 컨텍스트를 로드하여 테스트 환경 설정.
- @AutoConfigureMockMvc: MockMvc를 자동으로 설정하여 HTTP 요청을 모방할 수 있게 함.
- @DisplayName: 테스트 메소드에 설명을 추가하여 가독성을 높임.
- @Test: 이 메소드가 JUnit 테스트 메소드임을 표시.
- 필드 정의
- MockMvc: HTTP 요청을 테스트하기 위한 객체.
- ObjectMapper: Java 객체를 JSON 형태로 변환하기 위한 도구. 요청 및 응답 본문의 직렬화와 역직렬화를 처리.
- WebApplicationContext: Spring 애플리케이션의 웹 환경을 제공하며, MockMvc를 설정하는 데 사용.
- GuestBookRepository: 데이터베이스와의 상호작용을 위한 리포지토리입니다. 방명록 데이터를 저장하고 조회하는 데 사용.
- 테스트 준비
- @BeforeEach: 각 테스트 실행 전에 호출되며, MockMvc를 WebApplicationContext를 사용하여 설정하고, 테스트 데이터베이스를 초기화
- 방명록 추가 테스트
- addArticle 메소드는 방명록을 추가하는 API가 제대로 작동하는지 확인. 요청 본문을 JSON으로 직렬화한 후, MockMvc를 사용해 POST 요청을 보낸다. 결과적으로 상태 코드가 201 Created인지, 데이터베이스에 추가된 방명록이 존재하는지를 검증.
- 모든 방명록 조회 테스트
- findAllArticles 메소드는 모든 방명록을 조회하는 API가 제대로 작동하는지 테스트. 데이터베이스에 방명록을 추가한 후, GET 요청을 보내고, 반환된 데이터의 내용을 검증.
- 특정 방명록 조회 테스트
- findArticle 메소드는 특정 ID의 방명록을 조회하는 API를 테스트. 방명록을 추가한 후, 해당 ID로 GET 요청을 보내고, 응답 데이터가 예상한 값과 일치하는지 검증
- 방명록 삭제 테스트
- deleteArticle 메소드는 방명록을 삭제하는 API를 테스트. 방명록을 추가한 후, DELETE 요청을 보내고, 데이터베이스에서 방명록이 삭제되었는지 확인
- 방명록 수정 테스트
- updateArticle 메소드는 특정 방명록 글을 수정하는 API의 기능이 제대로 작동하는지를 검증.
그리고 테스트 컨트롤러를 실행해보면 테스트 통과 여부를 안내해 준다.

5개의 메소드 테스트가 모두 통과한 것을 확인할 수 있다.
[참고]
스프링 부트 3 백엔드 개발자 되기
다음 내용
[Java] Spring Boot: 테스트 코드
이전 내용 [Java] Spring Boot: 방명록 Rest API 구현(feat. MySQL 연동)이전 내용 [Java] Spring Boot: 코드, 요청&응답 과정 이해하기이전 내용 [Java] Spring Boot: Maven Repository이전 내용 [Java] 스프링부트: Spring Initial
puppy-foot-it.tistory.com