SpringBoot + Next.js 복습3

황정성·2025년 10월 22일

MVC 작업

Article controller에서 만들었던 테스트 코드 들은 지워버리자

package com.rest.proj.domain.article.controller;

import com.rest.proj.domain.article.entity.Article;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/articles")
public class ApiV1ArticleController {
    @GetMapping("")
    public List<Article> getArticles() {
        List<Article> articles = new ArrayList<>();
//        articles.add(new Article(1L));
//        articles.add(new Article(2L));
//        articles.add(new Article(3L));

        return articles;
    }

    @GetMapping("/{id}")
    public Article getArticle(@PathVariable("id") Long id) {
//        Article article = new Article((id));
        Article article = new Article();

        return article;
    }
}

단건조회 api에 빈객체를 하나 만들어 줬다

엔티티도 손좀 보자

package com.rest.proj.domain.article.entity;

import jakarta.persistence.Entity;
import lombok.*;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Article {
    private String subject;
    private String content;

}

제목과 내용 데이터를 넣기 위해 변수(칼럼)을 추가 했다.
어노테이션도 엔티티로서 역할을 하기위해 @Entity를 넣었고 그 이외에는 생성자와 게터, 세터를 활용하기 위해 넣어뒀다.

그리고 id를 뺀 이유는 모든 테이블에 공통적으로 있는 칼럼들은 베이스 엔티티에서 다루기 위해 일단 뺐다.

그리고 베이스 엔티티 클래스를 글로벌 패키지 안에 jpa.BaseEntity.java로 만들자!

package com.rest.proj.global.jpa;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Getter
@SuperBuilder
@MappedSuperclass
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
@ToString
public class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @CreatedDate
    private LocalDateTime createDate;

    @LastModifiedDate
    private LocalDateTime modifyDate;
}

이렇게 모든 테이블에 공통적으로 들어가는 id, 생성시각, 수정시각 을 넣었다

다시 Article 엔티티에서 설정좀하자

package com.rest.proj.domain.article.entity;

import com.rest.proj.global.jpa.BaseEntity;
import jakarta.persistence.Entity;
import lombok.*;
import lombok.experimental.SuperBuilder;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@ToString(callSuper = true)
public class Article extends BaseEntity {
    private Long id;
    private String subject;
    private String content;

}

일단 베이스 엔티티를 상속 받게 했다
@Superbuilder를 통해서 베이스 엔티티를 상속 받는 엔티티 설정을 한 것이다
@ToString에서 (callSuper = true)는 베이스 엔티티에 있는 것들도 적용시키겠다는 거다.

이 설정까지 끝났으면 메인클래스에서 @SpringBootApplicaiton 어노테이션 밑에 @EnableJpaAuditing 을 추가하자

서비스와 레파지토리를 만들어보자
artilce 패키지 안에
각각 service.Articleservice.java, repository.ArticleRepository.java를 만들고 레파지토리 설정좀 하자

package com.rest.proj.domain.article.repository;

import com.rest.proj.domain.article.entity.Article;
import org.springframework.data.jpa.repository.JpaRepository;

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

JpaRepository를 상속 받기 위해 클래스 대신 interface로 바꿨다. 그리고 JpaRepository 인자로 Article과, Long을 받을 수 있게 하였다.

NotProd

개발용으로 쓸 초기 데이터 클래스를 만들자
global패키지에서 initData.NotProd.java클래스를 만들자

package com.rest.proj.global.initData;

import com.rest.proj.domain.article.service.ArticleService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile({"dev", "test"})
public class NotProd {
    @Bean
    CommandLineRunner initData(ArticleService articleService) {
        return args -> {
            articleService.create("제목 1", "내용 1");
            articleService.create("제목 2", "내용 2");
            articleService.create("제목 3", "내용 3");
            articleService.create("제목 4", "내용 4");
            articleService.create("제목 5", "내용 5");
        };
    }
}

profile은 대충 알고 있는 것처럼 yml 지목하는 거다.
그리고 서비스에 create메서드가 없으니까 그냥 마우스 올려 서비스에 자동생성하게 하자

이제 서비스에서 crate메서드 수정과 서비스 세팅좀 하자

package com.rest.proj.domain.article.service;

import com.rest.proj.domain.article.entity.Article;
import com.rest.proj.domain.article.repository.ArticleRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ArticleService {
    private final ArticleRepository articleRepository;
    public void create(String subject, String content) {
        Article article = Article.builder()
                .subject(subject)
                .content(content)
                .build();
        articleRepository.save(article);
    }
}

h2콘솔에서 확인해보자

잘 들어갔다.

아티클 컨트롤러에서

package com.rest.proj.domain.article.controller;

import com.rest.proj.domain.article.entity.Article;
import com.rest.proj.domain.article.service.ArticleService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/articles")
public class ApiV1ArticleController {
    private final ArticleService articleService;

    @GetMapping("")
    public List<Article> getArticles() {
        List<Article> articles = articleService.getList();
//        articles.add(new Article(1L));
//        articles.add(new Article(2L));
//        articles.add(new Article(3L));

        return articles;
    }

    @GetMapping("/{id}")
    public Article getArticle(@PathVariable("id") Long id) {
//        Article article = new Article((id));
        Article article = new Article();

        return article;
    }
}

다건 조회 api를 이렇게 바꿨다.

getList가 아티클서비스에 없으니까 서비스에서 만들자!

package com.rest.proj.domain.article.service;

import com.rest.proj.domain.article.entity.Article;
import com.rest.proj.domain.article.repository.ArticleRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
@RequiredArgsConstructor
public class ArticleService {
    private final ArticleRepository articleRepository;

    public List<Article> getList() {
        return articleRepository.findAll();
    }

    public void create(String subject, String content) {
        Article article = Article.builder()
                .subject(subject)
                .content(content)
                .build();
        articleRepository.save(article);
    }

}

jpa를 안썼으면 쿼리문을 작성해야겠지만 jpa를 써서 자동으로 생성된다

단건도 수정해보자!
다시 컨트롤러에서

@GetMapping("/{id}")
    public Article getArticle(@PathVariable("id") Long id) {
//        Article article = new Article((id));
        Article article = articleService.getArticle(id);

        return article;
    }

단건조회 api를 이렇게 수정했다.
서비스에 getArticle이 없으니까 다시 서비스에서 수정해보자

public Article getArticle(Long id) {
        Optional<Article> oq = articleRepository.findById(id);

        if (oq.isEmpty()) {
            return null;
        }

        return oq.get();
    }

getList 메서드 밑에 해당 코드를 추가 했다
옵셔널로 아이디를 찾아 해당 아티클을 가져오게 했고 비어있으면 에러 처리를 해도 되지만 일단 null값을 반환하고 있으면 레파지토리에서 아이디를 통해 아티클을 가져오게 했다.

다건 조회는 잘 적용 되었다.

단건조회는 이렇게 뜨는데 api시큐리티 컨피그에서 해당 경로에대한 인가를 안해줬기 때문이다.

ApiScurityConfig에서 아티클스 인가 부분 밑에
.requestMatchers("/api/*/articles").permitAll() 이 코드 한 줄 추가하자!

잘된다

RsData 추가

데이터 형식을 맞추기 위해 RsData를 추가 할 예정이다.
global에서 rsData.RsData.java 클래스를 만들자!

package com.rest.proj.global.rsData;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class RsData<T> {
    private String resultCode;
    private String msg;
    private T data;
    public static <T> RsData<T> of(String resultCode, String msg, T data) {
        return new RsData<>(resultCode, msg, data);
    }
    public static <T> RsData<T> of(String resultCode, String msg) {
        return of(resultCode, msg, null);
    }
    @JsonIgnore
    public boolean isSuccess() {
        return resultCode.startsWith("S-");
    }
    @JsonIgnore
    public boolean isFail() {
        return !isSuccess();
    }
    
}

여러 타입의 데이터가 들어오기 때문에 제네릭 타입으로 받을 수 있게 했다

이제 아티클 컨트롤러가서 테스트 해볼 준비를 해보자

@GetMapping("")
    public RsData<List<Article>> getArticles() {
        List<Article> articles = articleService.getList();

        return RsData.of("S-1", "성공", articles);
    }

다건 조회 api를 수정했다

잘 나온다

0개의 댓글