스프링 부트 3 - 블로그 글 작성 API

김동헌·2023년 11월 11일
0

SpringBoot

목록 보기
7/19
post-thumbnail

전체적인 흐름

순서
엔티티 구성(리포지토리)서비스 클래스컨트롤러API 테스트


초기 세팅

application.yml

application.yml

spring:
        jpa:
                # 전송 쿼리 확인
                show-sql: true
                properties:
                        hibernate:
                                format_sql: true
                # 테이블 생성 후에 data.sql 실행
                defer-datasource-initialization: true

build.gradle

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.0.2'
    id 'io.spring.dependency-management' version '1.1.0'
}

group = 'org.example' // 지정한 그룹 이름
version = '1.0'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

📌 블로그 글 작성 API

☄️ 엔티티 구성

🔨 Article.java (빌더 패턴 x)

package org.example.SpringBoot.domain;

import jakarta.persistence.*;
import lombok.Builder;

@Entity // 엔티티로 지정
public class Article {

    @Id // 기본키 : id 필드
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 자동으로 1씩 증가, MySQL의 AUTO_INCREMENT
    @Column(name = "id", updatable = false) // 한번 저장된 후 수정 불가능 (영속화)
    private Long id;

    @Column(name = "title", nullable = false) // title은 not null로 지정
    private String title;

    @Column(name = "content", nullable = false) // name도 not null로 지정
    private  String content;

    @Builder // 빌더 패턴으로 객체 생성
    public Article(String title, String content) {
        this.title = title;
        this.content = content;
    }

    protected Article() { } // 기본 생성자

    public Long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }
}

☄️ @Builder

@Builder 애너테이션은 롬복에서 지원하는 애너테이션으로, Builder 애너테이션을 생성자 위에 입력하면 빌더 패턴 방식으로 객체를 생성할 수 있어 편리합니다.

빌더 패턴을 사용하면 객체를 유연하고 직관적으로 생성할 수 있어서 개발자들이 애용하는 디자인 패턴 입니다.
즉, 빌더 패턴을 사용하면 어느 필드에 어떤 값이 들어가는지 명시적으로 파악할 수 있습니다.

빌더 패턴 예시

// 빌더 패턴 사용 x
new Article("abc", "def");

// 빌더 패턴 사용 o
Article.builder()
	.title("abc")
    .content("def")
    .build();

어떤가요 ? 제가 볼때도 명시적으로 어떤 필드에 어떤 값이 들어가는지 파악이 쉽습니다! 빌더 패턴을 사용하지 않는 경우에는 어디에 "abc`가, 어디에 "def"가 들어가는지 파악하기 어렵지만, 빌더 패턴을 사용하니 title에 "abc"가, content에 "def"가 들어가는지 파악이 잘 되네요 : >

기존의 Article.java 코드를 수정해볼까요 ?

🔨 Article.java(빌더 패턴 o)

package org.example.SpringBoot.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity // 엔티티로 지정
@Getter // Getter 메서드 대체
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {

    @Id // 기본키 : id 필드
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키 자동으로 1씩 증가, MySQL의 AUTO_INCREMENT
    @Column(name = "id", updatable = false) // 한번 저장된 후 수정 불가능 (영속화)
    private Long id;

    @Column(name = "title", nullable = false) // title은 not null로 지정
    private String title;

    @Column(name = "content", nullable = false) // name도 not null로 지정
    private  String content;

    @Builder // 빌더 패턴으로 객체 생성
    public Article(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

getId(), getTitle() 의 게터 메서드들은 @Getter로 대치하고.
@NoArgsConstructor 애너테이션을 선언해 접근 제어자가 protected인 기본 생성자를 별도의 코드 protected Article() { } 코드 없이 생성했습니다.

이렇게 롬복의 애너테이션을 사용하면 가독성이 향상됩니다.


🔨 BlogRepository.java

package org.example.SpringBoot.repository;

import org.example.SpringBoot.domain.Article;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BlogRepository extends JpaRepository<Article, Long> { }

JpaRepository 클래스를 상속받을 때 엔티티 Ariticle과 엔티티의 기본키(id)의 자료형 Long을 인수로 넣도록 코드를 작성합니다.

여기까지가 엔티티 구성입니다. : >


☄️ 서비스 구성

🔨 @AllArgsConstructor

@AllArgsConstructorLombok의 어노테이션 중 하나로, 해당 클래스의 모든 필드에 대한 매개변수를 가진 생성자를 자동으로 생성합니다. 이 생성자는 클래스의 모든 멤버 변수를 매개변수로 받아서 인스턴스를 생성하는데 사용됩니다.

예를 들어, 다음과 같은 클래스가 있다고 가정해 봅시다:

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class Person {
    private String name;
    private int age;
}

@AllArgsConstructor을 사용하면 다음과 같은 생성자가 자동으로 생성됩니다:

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

즉, 모든 필드에 대한 생성자를 별도로 작성하지 않고도 @AllArgsConstructor을 사용하여 간단하게 생성자를 생성할 수 있습니다. 이는 주로 불변(immutable)한 객체를 만들 때 또는 다양한 객체를 초기화해야 할 때 편리하게 사용됩니다.


앞으로 코드 작성하시면서 빨간색 오류는 일단은 넘어갈게요 !
PostMan과 TestCode 실행에는 영향이 없습니다 : >

🔨 AddArticleRequest.java

AddArticleRequest.java 파일은 DTO(Data Transfer Object)로 계층끼리 데이터를 교환하기 위해 사용하는 객체입니다.

DAO → 데이터베이스와 연결되고 데이터를 조회하고 수정하는데 사용하는 객체로 데이터 수정과 관련된 로직이 포함
DTO → 단순하게 데이터를 옮기기 위해 사용하는 전달자 역할을 하는 객체, 별도의 비즈니스 로직을 포함하지 않음

package org.example.SpringBoot.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.example.SpringBoot.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();
    }

}

이 코드에서 눈여겨 볼 부분은 toEntity()는 빌더 패턴을 사용해 DTO를 엔티티로 만들어주는 부분입니다. 이 메서드는 추후 블로그 글을 추가할 떄 저장할 엔티티로 변환하는 용도입니다!


🔨 BlogService.java

package org.example.SpringBoot.service;

import lombok.RequiredArgsConstructor;
import org.example.SpringBoot.domain.Article;
import org.example.SpringBoot.dto.AddArticleRequest;
import org.example.SpringBoot.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());
    }
}

@RequiredArgsConstructor 애너테이션은 롬복에서 지원하는 애너테이션입니다.

빈으로 등록된 @Service 애너테이션은 해당 클래스를 서블릿 컨테이너로 등록합니다.

save() 메서드는 JpaRepository에서 지원하는 저장 메서드 save()AddArticleRequest 클래스에 저장된 값들을 article 데이터베이스에 저장합니다.

BlogRepositoryJpaRepository를 상속받고
JpaRepositoryCrudRepository를 상속받으며
CrudRepositorysave()메서드가 선언되어 있습니다.


☄️ 컨트롤러 구성

🔨 BlogApiController.java

package org.example.SpringBoot.controller;

import lombok.RequiredArgsConstructor;
import org.example.SpringBoot.domain.Article;
import org.example.SpringBoot.dto.AddArticleRequest;
import org.example.SpringBoot.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")
    public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request) {
        Article savedArticle = blogService.save(request);

        // 요청 자원이 성공적으로 생성 되었다면, 저장된 블로그 정보를 응답 객체에 담아 전송
        return ResponseEntity.status(HttpStatus.CREATED)
                .body(savedArticle);
    }
}

http://localhost:8080/api/articlesaddArticle() 메서드에 매핑됩니다.

@ReqyestBody 애너테이션은 HTTP 요청을 할 때 응답에 해당하는 값을 @RequestBody 애너테이션이 붙은 대상 객체인 AddArticleRequest에 매핑합니다.

@ResponseEntity.status().body()는 응답코드 201로 테이블에 저장된 객체를 반환합니다.


☄️ API 실행 테스트

🔨 Postman

HTTP 메서드POST로 Postman을 이용해 보내보도록 하겠습니다.

controller의 역할과 코드를 기억하시나요 ?
모든 요청의 처리는 controller에서 처리합니다. controller패키지의 BlogApiController.java에서 @PostMapping('api/articles") 코드에서 POST 요청을 처리하는 과정을 작성했습니다 !

빨간색 박스의 영역을 입력하고 Send를 누르게 되면
아래처럼 데이터가 삽입 됩니다.

이제 H2를 이용해 확인해보겠습니다 !


🔨 H2

H2를 사용하려면 resources 폴더 application.yml코드에 H2관련 코드를 넣어줘야 합니다 !


spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb
    username: sa
    password:
  h2:
    console:
      enabled: true
      path: /h2-console
      settings:
        web-allow-others: true
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
    defer-datasource-initialization: true

서버 주소에 http://localhost:8080/h2-console를 입력하시면 콘솔이 출력됩니다. application.ymldatasource: 부분의 driver-class-nameurl, username을 이용해 접속해봅시다 !

Test Conection을 선택하면 테스트 성공 이라고 뜨게 됩니다.
이제 Connect를 해보겠습니다 !

SQL 쿼리문 Select * from ARTICLE이 보이시나요 ?
쿼리문을 작성하고 Run을 누르게 되면 Postman으로 넣은 값이 저장되어 있는걸 확인할 수 있습니다 !


🔨 테스트 코드

개발하는 과정에서 PostmanH2를 사용해 매번 테스트하는 과정은 비효율적입니다. 따라서 이러한 과정들을 대신할 테스트 코드를 작성해보겠습니다.

BlogApiController 클래스에서 ALT + ENTER를 누르면 Create Test로 테스트 코드를 생성하겠습니다.

Given : 블로그 글 추가에 필요한 요청 객체를 생성

When : 블로그 글 추가 API에 요청. 요청 타입은 JSON이며, Given에서 미리 만들어둔 객체를 요청 본문으로 함께 전송

Then : 응답 코드가 201 Created인지 검증.
Blog를 전체 조회해 크기가 1인지 확인, 실제로 저장된 데이터와 요청 값을 비교

블로그 테스트 코드 assertThat() 메서드 다른 테스트들은 아래에 표로 정리했습니다 : >

코드설명
assertThat(articles.size()).isEqualTo(1);블로그 글 크기가 1개여야 한다.
assertThat(articles.size()).isGreaterThan(2);블로그 글 크기가 2보다 커야한다.
assertThat(articles.size()).isLessThan(5);블로그 글 크기가 5보다 작아야한다.
assertThat(articles.size()).isisZero();블로그 글 크기가 0이어야 한다.
assertThat(articles.title()).isEqualTo("제목");블로그 글의 title 값은 "제목"이어야 한다.
assertThat(articles.title()).isNotEmpty();블로그 글의 title 값이 비어있지 않아야 한다.
assertThat(articles.size()).contains("제");블로그 글의 title 값이 "제"를 포함해야 한다.

🔨 직렬화, 역직렬화

HTTP에서는 JSON을 사용하고, Java에서는 객체를 사용합니다. 서로 다른 형식을 맞춰주기 위해 변환 작업을 제공하는 것이 ObjectMapper 클래스입니다.

직렬화 : 자바 시스템 내부에서 사용되는 객체를 외부에서 사용하도록 데이터를 변환

역직렬화 : 직렬화의 반대로, 외부에서 사용하는 데이터를 자바의 객체 형태로 변환



☄️ h2 접속 - 트러블 슈팅

하... h2 접속하실 때 404 not found 로 접속이 안되실텐데,,
여러 자료들을 찾아 봤는데,, 정말 간단히 해결되었습니다..

implementation 'org.springframework.boot:spring-boot-devtools' 이거 추가해주니 정상 작동 합니다....

추가적으로 JDBC URL 접속도 안됩니다,,
그래서 로그를 보시면

2023-11-11T22:57:18.483+09:00  INFO 20340 --- [  restartedMain] o.s.b.a.h2.H2ConsoleAutoConfiguration    : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:75c836b2 ~'

이 로그에서 마지막 뒤의 jdbc:h2:mem:75c836b2 ~ 이 부분들로 접속하시면 접속 됩니다 !!

아무래도 로그를 보고 이리저리 확인해본 결과 application.yml 파일이 적용이 안되는 것 같아 파일 구조를 잘 살펴보니,, 프로젝트를 생성할 때 Build SystemGradle가 아닌 IntelliJ였나,, 프로젝트 생성 문제였습니다..

이 구조에서 resources의 노란색,,아이콘이 정상인데

그냥 패키지로 되어 있더라고요 ㅎㅎ...ㅎ...ㅎ......3시갆ㅎㅎㅎㅎ,....

implementation 'org.springframework.boot:spring-boot-devtools' 이거 안해도 됩니다 ㅎㅎ...ㅎ...yml을 설정 안하셨다면 이전의 트러블 슈팅 방법으로 하시면 됩니다 ㅎㅎ...ㅎ...ㅎ...ㅎ....

resources 폴더의 노란색 아이콘은 일반적으로 Java 프로젝트 구조에서, 특히 Maven 또는 Gradle을 사용할 때 특수 디렉터리임을 나타냅니다. 이 폴더는 속성 파일, XML 구성 및 애플리케이션이 런타임에 필요로 하는 기타 비코드 리소스와 같은 리소스를 위해 지정됩니다. 이러한 리소스는 애플리케이션 내에서 쉽게 액세스할 수 있도록 클래스 경로에 포함되는 경우가 많습니다.

profile
백엔드 기록 공간😁

0개의 댓글