스프링 부트에 대한 사전 지식은 어느 정도 공부했으니 이제 블로그 개발을 구현해 볼 것이다. 그럼 가 보자고! 👏
컬럼명 | 자료형 | null 허용 | 키 | 설명 |
---|---|---|---|---|
id | BIGINT | N | 기본키 | 일련번호, 기본키 |
title | VARCHAR(255) | N | 게시물의 제목 | |
content | VARCHAR(255) | N | 내용 |
만들 엔티티와 매핑되는 테이블 구조는 위와 같다.
package me.ansoohyeon.springbootdeveloper.domain;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity // 엔티티로 지정
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Article {
@Id // id 필드를 기본키로 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키를 자동으로 1씩 증가
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "title", nullable = false) // 'title'이라는 not null 컬럼과 매핑
private String title;
@Column(name = "content", nullable = false)
private String content;
@Builder // 빌더 패턴으로 객체 생성
public Article(String title, String content){
this.title = title;
this.content = content;
}
}
@Builder 어노테이션이란?
롬복에서 지원하는 어노테이션으로, 이 어노테이션을 생성자 위에 입력하면 빌더 패턴 방식으로 객체를 생성할 수 있어 편리하다.
빌더 패턴을 사용하면 객체를 유연하고 직관적으로 생성할 수 있기 때문에 개발자들이 애용하는 디자인 패턴이다. 즉, 빌더 패턴을 사용하면 어느 필드에 어떤 값이 들어가는지 명시적으로 파악할 수 있다.// 빌더 패턴을 사용하지 않았을 때 new Article("abc", "def"); // 빌더 패턴을 사용했을 때 Article.builder() .title("abc") .content("def") .build();
이제 레포지토리를 만들어 준다.
package me.ansoohyeon.springbootdeveloper.repository;
import me.ansoohyeon.springbootdeveloper.domain.Article;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BlogRepository extends JpaRepository<Article, Long> {
}
전체적인 구조는 이러하다. (빨간 줄은 일단 무시.. 😅)
package me.ansoohyeon.springbootdeveloper.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import me.ansoohyeon.springbootdeveloper.domain.Article;
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class AddArticleRequest {
private String title;
private String content;
public Article toEntity(){ // 생성자를 사용해 객체 생성
return Article.builder()
.title(title)
.content(content)
.build();
}
}
package me.ansoohyeon.springbootdeveloper.service;
import lombok.RequiredArgsConstructor;
import me.ansoohyeon.springbootdeveloper.domain.Article;
import me.ansoohyeon.springbootdeveloper.dto.AddArticleRequest;
import me.ansoohyeon.springbootdeveloper.repository.BlogRepository;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가
@Service
public class BlogService {
private final BlogRepository blogRepository;
// 블로그 글 추가 메소드
public Article save(AddArticleRequest request){
return blogRepository.save(request.toEntity());
}
}
package me.ansoohyeon.springbootdeveloper.controller;
import lombok.RequiredArgsConstructor;
import me.ansoohyeon.springbootdeveloper.domain.Article;
import me.ansoohyeon.springbootdeveloper.dto.AddArticleRequest;
import me.ansoohyeon.springbootdeveloper.service.BlogService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RequiredArgsConstructor
@RestController // HTTP Response Body에 객체 데이터를 JSON 형식으로 반환하는 컨트롤러
public class BlogApiController {
private final BlogService blogService;
// HTTP 메소드가 POST일 때 전달받은 URL과 동일하면 메소드로 매핑
@PostMapping("/api/articles")
// @RequestBody로 요청 본문 값 매핑
public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request){
Article savedArticle = blogService.save(request);
// 요청한 자원이 성공적으로 생성되었으며 저장된 블로그 글 정보를 응답 객체에 담아 전송
return ResponseEntity.status(HttpStatus.CREATED)
.body(savedArticle);
}
}
spring:
jpa:
# 전송 쿼리 확인
show-sql: true
properties:
hibernate:
format_sql: true
# 테이블 생성 후에 data.sql 실행
defer-datasource-initialization: true
datasource:
url: jdbc:h2:mem:testdb // H2 콘솔 활성화
h2:
console:
enabled: true
이제 스프링 부트 서버를 실행하고, 포스트맨으로 가 보자. 포스트맨에서 POST 방식으로 아까 지정한 주소 (http://localhost:8080/api/articles)를 입력한다. 그리고 Body 탭에서 raw - JSON
을 선택하고 아래와 같이 작성한다.
{
"title" : "제목",
"content" : "내용"
}
그리고 'Send' 버튼을 클릭하면 응답 값이 반환된다. 데이터가 제대로 삽입된 것 같다! H2 데이터베이스에 잘 저장되었는지 확인해 보자.
웹 브라우저에서 http://localhost:8080/h2-console 에 접속해 보자. JDBC URL을 jdbc:h2:mem:testdb
로 입력 후 Connect 한다. 그러면 스프링 부트 서버 안에 내장되어 있는 H2 데이터베이스에 접속하고 데이터를 확인할 수 있다.
전체 조회하는 SQL문을 입력하고 Run을 클릭하면 정말로 아까 삽입한 데이터가 제대로 들어가 있는 모습이다! (개인적으로 직접 DML을 입력하던 경험이 많은 나로서 너무 신기했음 👀)
이처럼 애플리케이션을 실행하면 자동으로 생성한 엔티티 내용을 바탕으로 테이블이 생성되고, 내가 요청한 POST 요청에 의해 데이터가 실제로 저장된 것을 볼 수 있다.
이제 반복 작업을 줄여 줄 테스트 코드를 작성해 보자.
BlogApiController
의 클래스 이름에 마우스를 놓고 Alt+Enter
로 테스트를 생성한다. 그리고 테스트할 코드를 작성해 준다.
package me.ansoohyeon.springbootdeveloper.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import me.ansoohyeon.springbootdeveloper.domain.Article;
import me.ansoohyeon.springbootdeveloper.dto.AddArticleRequest;
import me.ansoohyeon.springbootdeveloper.repository.BlogRepository;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.List;
@SpringBootTest // 테스트용 애플리케이션 컨텍스트
@AutoConfigureMockMvc // MockMvc 생성 및 자동 구성
class BlogApiControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper; // 직렬화, 역직렬화를 위한 클래스
@Autowired
private WebApplicationContext context;
@Autowired
BlogRepository blogRepository;
@BeforeEach // 테스트 실행 전 실행하는 메소드
public void mockMvcSetUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
blogRepository.deleteAll();
}
@DisplayName("addArticle: 블로그 글 추가에 성공한다.")
@Test
public void addArticle() throws Exception{
// given
final String url = "/api/articles";
final String title = "title";
final String content = "content";
final AddArticleRequest userRequest = new AddArticleRequest(title, 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 = blogRepository.findAll();
assertThat(articles.size()).isEqualTo(1); // 크기가 1인지 검증
assertThat(articles.get(0).getTitle()).isEqualTo(title);
assertThat(articles.get(0).getContent()).isEqualTo(content);
}
}
테스트 코드를 실행해 보니 코드가 잘 동작하는 것 같다! 👌
다음 포스팅에서는 블로그 글 목록 조회를 위한 API를 구현해 볼 것이다.