김쥬르에 개발일지
Spring Boot 블로그 글 추가하기 본문
이제 개발에 필요한 기초지식들을 알아보았으니
본격적으로 블로그 개발을 구현해보겠습니다.
시작하기 전에 앞서 다뤘던 예제 파일
application.properties를 제외한 나머지를 삭제하겠습니다.

파일 구성이 이렇게 되도록 삭제하시면 되겠습니다.
만약 공부했던 내용을 그대로 두고싶다면 application.properties만 따로 복사해서
새 프로젝트를 생성해주세요
Entity
먼저 엔티티를 구성하겠습니다.
테이블의 구조는 다음과 같습니다.
| 컬럼명 | 자료형 | null 허용 | 키 | 설명 |
| id | BIGINT | N | 기본키(pk) | 일련번호,기본키 |
| title | VARCHAR(255) | N | 게시물의 제목 | |
| content | VARCHAR(255) | N | 내용 |
springboot 패키지에 domain 패키지를 새로 만들고
domain 패키지에 Article.java 클래스를 생성하겠습니다.
@Entity
public class Article {
@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;
}
protected Atricle() { //기본 생성자
}
// Getter
publc Long getId(){
return id;
}
public String getTitle(){
return title;
}
public String getContent() {
return content;
}
}
@Builder 어노테이션은 롬복에서 지원하는 어노테이션입니다.
생성자 위에 입력하면 빌더 패턴 방식으로 객체를 생성할 수 있어 편리합니다.
빌더패턴을 사용하면 객체를 유연하고 직관적으로 생성할수 있기 때문에
개발자들이 애용하는 디자인 패턴입니다.
예시를 한번 볼까요?
// 빌더 패턴을 사용하지 않았을 때
new Article("abc" , "def");
//빌더 패턴을 사용했을 때
Article.builder()
.title("abc")
.content("def")
.builder();
사용하지 않을대는 어느 필드에 어떤 값이 들어가는지 파악하기 어렵지만
사용하였을 경우 직관적으로 보이기 때문에 코드의 가독성을 높여줍니다.
또한 Getter 와 @NoArgsConstructor 사용하여
코드를 깔끔하고 가독성있게 만들수있습니다.
앞선 포스팅들에서 언급 드린적있죠?
이번엔 썻을때와 안썻을때를 비교하여 체감해봅시다.
Getter , @NoArgsConstructor 어노테이션을 추가한다면
주석으로 표시한 기본생성자와 Getter를 자동으로 생성해주기에
코드에 직접 입력해줄 필요가없습니다.
해당 코드를 지우고 어노테이션을 클래스 명 위에 작성해봅시다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {
@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;
}
}
한눈에 봐도 깔끔해졌죠? 이렇게 롬복의 어노테이션을 사용하면
코드를 반복해 입력할 필요가 없어져서 가독성이 향상됩니다.
이번엔 리포지터리를 만들겠습니다.
spring boot 패키지에 repository 패키지를 새로 만든후
BlogRepository.java 파일을 생성후 인터페이스후 아래처럼 작성해주세요
public interface BlogRepository extends JpaRepository<Article, Long> {
}
JpaRepository 클래스를 상속받을때 엔티티 Article과 엔티티의 PK타입인 Long을
인수로 넣었습니다 이제 BlogRepository를 사용할때 JpaRepository에서 제공하는
여러 메서드들을 사용할수 있게 되었습니다
단 Article 엔티티에 관한 로직만을 다루기에 추후 엔티티가 추가된다면 다시
그의 관한 리포지터리를 만들어주어야합니다.
이제 API를 하나씩 구현해보겠습니다.
구현 과정은 서비스 클래스에서 메서드를 구현하고
컨트롤러에서 사용할 메서드를 구현한 다음
API를 실제로 테스트해보겠습니다.

DTO
springboot 패키지에 dto 패키지를 생성한 다음
dto 패키지를 컨트롤러에서 요청 본문을 받을 객체인 AddArticleRequest.java 파일을 생성합니다.
DTO는 계층끼리 데이터를 교환하기 위해 사용하는 객체입니다.
단순하게 데이터를 옮기기 위해 사용하는 전달자 역할을 하는 객체이므로
별도의 비즈니스 로직을 포함하지 않습니다.
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class AddArticleRequest {
private String title;
private String content;
public Article toEntity(String author) {
return Article.builder()
.title(title)
.content(content)
.build();
}
}
toEntity()는 빌더 패턴을 사용해 DTO를 엔티티로 만들어주는 메서드입니다.
추후에 블로그 글을 추가할 때 저장할 엔티티로 변환하는 용도로 사용합니다.
DTO를 엔티티로 만들어준다니 이게 무슨말일까요?
앞서 DTO는 계층끼리 데이터를 교환하기 위한 객체라고했습니다.
엔티티 클래스는 데이터베이스와 맞닿는 핵심 클래스이며
Entity클래스를 기준으로 테이블이 생성되고 스키마가 변경됩니다.
그렇기 때문에 다양한 계층에서 Entity를 직접적으로 사용하게 된다면
원치않게 Entity의 속성을 변경시킬 위험이 존재하며, Entity의 모든 속성이
불필요하게 외부에 노출될 가능성이 있습니다.
그렇기 때문에 우리는 DTO 를 사용하며 Entity 클래스에서 필요한
데이터만을 선택적으로 DTO에 담아서 생성해 사용하는겁니다.
Service
springboot 패키지에 service 패키지를 생성한뒤
BlogService.java 클래스를 구현해봅시다.
@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가
@Service // 빈으로 등록
public class BlogService {
private final BlogRepository blogRepository;
// 블로그 글 추가 메서드
public Article save(AddArticleRequest request) { // 해당 줄 save는 주어진 블록에 메서드명 헷갈리지말기
return blogRepository.save(request.toEntity()); // save() 메서드는 여기
}
}
@RequiredArgsConstructor는 롬복에서 지원하는 빈을 생성자로 생성하는 어노테이션입니다.
final 이나 @NotNull이 붙은 필드로 생성자를 만들어줍니다.
@Service 어노테이션은 해당 클래스를 빈으로 서블릿 컨테이너에 등록해줍니다.
save() 메서드는 JpaRepository에서 지원하는 저장 메서드로
AddArticleRequest 클래스에 저장된 값들을 article 데이터베이스에 저장합니다.
Controller
이제 URL에 매핑하기 위한 컨트롤러 메서드를 추가하겠습니다.
spring패키지에 controller 패키지를 생성한뒤
BlogApiController.java 파일을 생성후 코드를 입력합시다.
@RequiredArgsConstructor
@RestController // HTTP Response Body에 객체 데이터를 JSON 형식으로 반환하는 컨트롤러
public class BlogApiController {
private final BlogService blogService;
// HTTP 메서드가 POST일때 전달받은 URL와 동일하면 메서드로 매핑
@PostMapping("/api/articles")
// 요청 본문 값 매핑
public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request) {
Article savedArticle = blogService.save(request);
//요청한 자원이 성공적으로 생성되었으며 저장된 블로그 글 정보를 응답 객체에 담아 전송
return ResponseEntity.status(HttpStatus.CREATED)
.body(savedArticle);
}
}
@RestController
HTTP 응답으로 객체 데이터를 JSON 형식으로 반환합니다.
@PostMapping
HTTP 메서드가 POST일 때 요청받은 URL와 동일한 메서드와 매핑합니다.
지금의 경우 /api/articles는 addArticle() 메서드에 매핑합니다
@RequestBody
HTTP를 요청할 때 응답에 해당하는 값을 RequestBody 어노테이션이 붙은 대상 객체에 매핑합니다.
지금의 경우 AddArticleRequest에 매핑하여 request라는 이름으로 값을 전달합니다.
ResponseEntity.status().body()
응답코드로 201, 즉 created를 응답하고 테이블에 저장된 객체를 반환합니다.
응답코드 목록
.200 OK : 요청이 성공적으로 수행되었음
.201 Created : 요청이 성공적으로 수행되었고 새로운 리소스가 생성되었음
.400 Bad Request : 요청 값이 잘못되어 요청에 실패했음
.403 Forbidden : 권한이 없어 요청에 실패했음
.404 Not Found : 요청 값으로 찾은 리소스가 없어 요청에 실패했음
.500 internal Server Error : 서버 상에 문제가 있어 요청에 실패했음
이제 실제 테이터를 확인하기 위해 실행 테스트를 해보겠습니다.
스프링부트 서버를 실행해주고
POSTMAN에 접속합니다.

HTTP메서드 : POST
URL : http://localhost:8080/api/articles
Body : raw -> Json 으로 변경한 다음
표시한 요청창에 위와 같이 작성한다음 send를 눌러보세요
아래쪽 body에 결과를 보여줄겁니다.
위와 같이 나온다면 요청 성공입니다.
실제로도 DB에 저장이 잘되었는지 볼까요?

DB에도 데이터가 잘 저장되었네요
앞으로 개발하면서 이런 테스트 과정을 계속 거쳐야 할텐데요
매번 이런 방식으로 테스트하려면 매우 불편합니다.
작업을 줄여줄 테스트 코드를 작성해보겠습니다.
BlogApiController 클래스에 test 코드 파일을 생성하세요.
파일 생성 방법은 전 포스팅에서 설명하였기에 생략하겠습니다.
@SpringBootTest
@AutoConfigureMockMvc
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();
}
}
테스트 관련 어노테이션은 전 포스팅에서 알아보았던것들인데
ObjectMapper 클래스는 어떤걸까요?
ObjectMapper?
이 클래스로 만든 객체는 자바 객체를 JSON 데이터로 변환하는 직렬화
또는 반대로 JSON 데이터를 자바에서 사용하기 위해 자바 객체로
변환하는 역직렬화를 할때 사용합니다.
직렬화? 역직렬화?
HTTP에서는 JSON을 자바에서는 객체를 사용합니다
서로 형식이 다르기 때문에 형식에 맞게 변환하는 작업이 필요한대요
이런 작업을 직렬화 , 역직렬화라고 합니다.
직렬화란 자바 시스템 내부에서 사용되는 객체를 외부에서 사용하도록 데이터를 변환하는 작업
역직렬화는 직렬화에 반대입니다.

이제 블로그 글 생성 api를 테스트하는 코드를 작성해봅시다.
생성패턴
GIVEN : 블로그 글 추가에 필요한 요청 객체를 만듭니다.
WHEN : 블로그 글 추가 API에 요청을 보내고 요청 타입은 JSON이며 GIVEN 절에서 미리 만들어준 객체를 요청 본문으로 함께 보냅니다.
THEN : :응답 코드가 201 Created 인지 확인하고 Blog를 전체 조회해 크기가 1인지 확인하고 실제로 저장된 데이터와 요청값을 비교합니다.
@SpringBootTest
@AutoConfigureMockMvc
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);
}
}
writeValueAsString()
객체를 JSON 으로 직렬화 해줍니다.
aseertThat()
.isEqualTo(1)블로그 글의 개수가 1인지 확인합니다.
.get 첫 번째 요청의 결과가 같은지 검증합니다
앞서 다 배운 내용이기에 설명은 생략하겠습니다.
테스트 코드를 실행해 코드가 잘동작하는지 확인합니다.

다음은 포스팅은 글 조회에 대해 알아보겠습니다.
'Spring boot' 카테고리의 다른 글
| Spring Boot 수정 삭제 (0) | 2023.12.28 |
|---|---|
| Spring Boot 블로그 글 조회 (0) | 2023.12.28 |
| Spring Boot REST API (2) | 2023.12.26 |
| SpringBoot ORM (0) | 2023.12.25 |
| SpringBoot 테스트 코드 (0) | 2023.12.25 |