
๐ https://github.com/LEEEUNCHEOL96/demo241106
์์
์งํ ์์
1. IntelliJ ๊ฐ๋ฐ ํ๊ฒฝ ์ค์
2. BaseEntity ๋ฐ ArticleEntity ์ค์
3. Article Controller, DTO, RsData ์ค์
4. ์์ฒญ ๋ฐ ์๋ต ์ค์
5. Service ์ค์
6. ์ปจํธ๋กค๋ฌ์ ๊ตฌํํ๊ธฐ
IntelliJ ๊ฐ๋ฐํ๊ฒฝ ์ค์
MariaDB ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๊ณ , JPA์ Hibernate๋ฅผ ํ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ํจ์จ์ ์ธ ์ฐ๋.
application.yml ํ์ผ์์ ์ฃผ์ ์ค์ ํญ๋ชฉ์ ์ค๋ช ํ ๋ด์ฉ์ด๋ค.
// application.yml
spring:
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://localhost:3306/demo241106
username: *****
password: *****
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
show_sql: true
format_sql: true
use_sql_comments: true
logging:
level:
root: INFO
example.demo.rest_api: DEBUG
org.hibernate.orm.jdbc.bind: TRACE
org.hibernate.orm.jdbc.extract: TRACE

// global.jpa > BaseEntity
@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 createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
}
์ด๋ ธํ ์ด์
- @SuperBuilder: ๋น๋ ํจํด์ ํตํด ๊ฐ์ฒด๋ฅผ ์์ฑํ ์ ์๊ฒ ์ง์, ์์ ๊ด๊ณ์์๋ ์ฌ์ฉ ๊ฐ๋ฅ.
- @MappedSuperclass: ์ด ํด๋์ค๋ ์ง์ ํ ์ด๋ธ์ ๋งคํ๋์ง ์์ง๋ง, ์์๋ฐ์ ํด๋์ค๋ค์ด ์ด๋ฅผ ์์ํ์ฌ ๋งคํ๋ ์ ์๋๋ก ํจ.
- @EntityListeners(AuditingEntityListener.class): Auditing ๊ธฐ๋ฅ์ ํ์ฑํํ์ฌ createdDate์ modifiedDate ํ๋๊ฐ ์๋์ผ๋ก ๊ด๋ฆฌ๋๋๋ก ํจ.
// domain.article.entity > Article
@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@ToString(callSuper = true)
public class Article extends BaseEntity {
private String subject;
private String content;
private String author;
}
Entity ๋ฅผ ์ด๊ธฐ ์ค์ ํ๋ ์ด์
JPA์ Spring Data JPA์์ ์ํฐํฐ๋ฅผ ์ค์ฌ์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ํธ์์ฉ์ ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ด๋ค. Article ์ํฐํฐ์ ๊ฐ์ JPA ์ํฐํฐ ํด๋์ค๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ๊ณผ ๋งคํ๋๋ ๊ฐ์ฒด๋ก, ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ดํฐ ๋ชจ๋ธ์ ์ ์ํ๊ณ , ๋ฐ์ดํฐ๋ฒ ์ด์ค CRUD ์์ ์ ์ํํ๋ ๋ฐ ํต์ฌ์ ์ธ ์ญํ ์ ํ๋ค.
์ด๋ฒ ์์ ์์๋ HTML ๊ธฐ๋ฐ์ ํ๋ก ํธ์๋ ๊ตฌํ ์์ด, Postman์ ์ด์ฉํ์ฌ API ํ ์คํธ ๋ฐ ๊ฒ์ฆ์ ์งํํ ์์ ์ด๋ค.
//domain.article.dto > ArticleDTO
@Getter
public class ArticleDTO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private final Long id;
private final String subject;
private final String content;
private final String author;
private final LocalDateTime createdDate;
@LastModifiedDate
private final LocalDateTime modifiedDate;
public ArticleDTO(Article article){
this.id = article.getId();
this.subject = article.getSubject();
this.content = article.getContent();
this.author = article.getAuthor();
this.createdDate = article.getCreatedDate();
this.modifiedDate = article.getModifiedDate();
}
}
DTO ๋ฅผ ๋ง๋ ์ด์
์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ ๊ณ์ธต ๊ฐ ๋ฐ์ดํฐ ๊ตํ์ ๋ณด๋ค ๋ช ํํ๊ณ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํด์์ด๋ค. ํนํ ArticleDTO๋ Article ์ํฐํฐ์ ์ปจํธ๋กค๋ฌ ๋๋ ์๋น์ค ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๋ ๋ฐ ์ฌ์ฉ๋๋ค. ๋ํ ์ ์ง๋ณด์์ ํ์ฅ์ฑ์ ๋์ผ ์ ์๋ค.
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/demo241106/articles")
public class ArticleController {
private final ArticleService articleService;
@GetMapping("") // ๋ค๊ฑด์กฐํ
public List<ArticleDTO> list() {
List<ArticleDTO> articleDTOS = new ArrayList<>();
Article article = new Article("์ฒซ ๋ฒ์งธ ๊ฒ์๊ธ", "๋ด์ฉ์
๋๋ค.", "์์ฑ์");
articleDTOS.add(new ArticleDTO(article));
Article article2 = new Article("๋ ๋ฒ์งธ ๊ฒ์๊ธ", "๋ด์ฉ์
๋๋ค.2", "์์ฑ์2");
articleDTOS.add(new ArticleDTO(article2));
Article article3 = new Article("์ธ ๋ฒ์งธ ๊ฒ์๊ธ", "๋ด์ฉ์
๋๋ค.3", "์์ฑ์3");
articleDTOS.add(new ArticleDTO(article3));
return articleDTOS;
}
@GetExchange("/{id}") //๋จ๊ฑด์กฐํ
public ArticleDTO getArticle(@PathVariable("id") Long id){
Article article = new Article("์ฒซ ๋ฒ์งธ ๊ฒ์๊ธ","๋ด์ฉ์
๋๋ค.","์์ฑ์");
ArticleDTO articleDTO = new ArticleDTO(article);
return articleDTO;
}
@PostMapping("") // ์์ฑ
public String create(@RequestParam("subject") String subject,
@RequestParam("content") String content,
@RequestParam("author") String author){
System.out.println(subject);
System.out.println(content);
System.out.println(author);
return "๋ฑ๋ก";
}
@PatchMapping("/{id}") // ์์
public String modify(@PathVariable("id") Long id,
@RequestParam("subject") String subject,
@RequestParam("content") String content,
@RequestParam("author") String author){
System.out.println(id);
System.out.println(subject);
System.out.println(content);
System.out.println(author);
return "์์ ";
}
@DeleteMapping("/{id}") // ์๊ฒ
public String delete(@PathVariable("id") Long id){
System.out.println(id);
return "์ญ์ ";
}
}
Postman์์ ์์ ArticleController์ REST API๋ฅผ ๊ฐ๋จํ๊ฒ ํ ์คํธํด๋ณด์. ๊ฐ ์๋ํฌ์ธํธ์ ๋ง๋ ์์ฒญ์ ๋ณด๋ด๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋ค.
global.RsData > RsData
@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("200");
}
@JsonIgnore
public boolean isFail() {
return !isSuccess();
}
}
RsData๋?
API ์๋ต์ ์ผ๊ด๋ ํ์์ผ๋ก ๋ฐํํ๊ธฐ ์ํด ์ฌ์ฉ๋๋ค. ์ฃผ๋ก ์๋ต ์ฝ๋, ๋ฉ์์ง, ๊ทธ๋ฆฌ๊ณ ์๋ต ๋ฐ์ดํฐ๋ฅผ ํฌํจํ๋ ๊ฐ์ฒด๋ก, ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌํ API ์๋ต์ ๊ตฌ์กฐ๋ฅผ ์ ์๋๋ค.
Controller ์ RsData ๋ฅผ ์ ์ฉ์ํค๊ธฐ ์ํด ์๋ต ๋ฐ ์์ฒญ์ ์์ฑํ๋ค.
์์ฒญ
// domain.article.request > ArticleCreateRequest
// domain.article.request > ArticleModifyRequest
@Getter
@Setter
public class Article_____Request{
@NotBlank
private String subject;
@NotBlank
private String content;
@NotBlank
private String author;
}
// domain.article.response > ArticleResponse ๋จ๊ฑด
// domain.article.response > ArticlesResponse ๋ค๊ฑด
@Getter
@AllArgsConstructor
public class ArticleResponse {
private final ArticleDTO article;
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/demo241106/articles")
public class ArticleController {
private final ArticleService articleService;
@GetMapping("") // ๋ค๊ฑด์กฐํ
public RsData<ArticlesResponse> list() {
List<ArticleDTO> articleDTOS = new ArrayList<>();
Article article = new Article("์ฒซ ๋ฒ์งธ ๊ฒ์๊ธ", "๋ด์ฉ์
๋๋ค.", "์์ฑ์");
articleDTOS.add(new ArticleDTO(article));
Article article2 = new Article("๋ ๋ฒ์งธ ๊ฒ์๊ธ", "๋ด์ฉ์
๋๋ค.2", "์์ฑ์2");
articleDTOS.add(new ArticleDTO(article2));
Article article3 = new Article("์ธ ๋ฒ์งธ ๊ฒ์๊ธ", "๋ด์ฉ์
๋๋ค.3", "์์ฑ์3");
articleDTOS.add(new ArticleDTO(article3));
return RsData.of("200","๊ฒ์๊ธ ๋ค๊ฑด ์กฐํ ์ฑ๊ณต", new ArticlesResponse(articleDTOS));
}
@GetExchange("/{id}") //๋จ๊ฑด์กฐํ
public RsData<ArticleResponse> getArticle(@PathVariable("id") Long id){
Article article = new Article("์ฒซ ๋ฒ์งธ ๊ฒ์๊ธ","๋ด์ฉ์
๋๋ค.","์์ฑ์");
ArticleDTO articleDTO = new ArticleDTO(article);
return RsData.of("200","๊ฒ์๊ธ ๋จ๊ฑด ์กฐํ ์ฑ๊ณต", new ArticleResponse(articleDTO));
}
@PostMapping("") // ์์ฑ
public String create(@Valid @RequestBody ArticleCreateRequest articleCreateRequest){
System.out.println(articleCreateRequest.getSubject());
System.out.println(articleCreateRequest.getContent());
System.out.println(articleCreateRequest.getAuthor());
return "๋ฑ๋ก์๋ฃ";
}
@PatchMapping("/{id}") // ์์
public String modify(@PathVariable("id") Long id, @Valid @RequestBody ArticleModifyRequest articleModifyRequest){
System.out.println(id);
System.out.println(articleModifyRequest.getSubject());
System.out.println(articleModifyRequest.getContent());
System.out.println(articleModifyRequest.getAuthor());
return "์์ ";
}
@DeleteMapping("/{id}") // ์๊ฒ
public String delete(@PathVariable("id") Long id){
System.out.println(id);
return "์ญ์ ";
}
}
@Service
@RequiredArgsConstructor
public class ArticleService {
private final ArticleRepository articleRepository;
public List<ArticleDTO> getList() {
List<Article> articleList = this.articleRepository.findAll();
List<ArticleDTO> articleDTOList = articleList.stream()
.map(article -> new ArticleDTO(article))
.collect(Collectors.toList());
return articleDTOList;
}
public Article getArticle(Long id) {
Optional<Article> optionalArticle = this.articleRepository.findById(id);
return optionalArticle.orElse(null);
}
public Article write(String subject, String content,String author){
Article article = Article.builder()
.subject(subject)
.content(content)
.author(author)
.build();
this.articleRepository.save(article);
return article;
}
public Article update(Article article, String content, String subject,String author) {
article.setSubject(subject);
article.setContent(content);
article.setAuthor(author);
this.articleRepository.save(article);
return article;
}
public void delete(Article article) {
this.articleRepository.delete(article);
}
}
์๋น์ค ๊ณ์ธต
// domain.article.response > ArticleCreateResponse ์์ฑ
// domain.article.response > ArticlesModifyResponse ์์
@Getter
@AllArgsConstructor
public class Article______Response {
private final Article article;
}
why delete response ๋ ๋ง๋ค์ง ์๊ณ articleResponse ๋ฅผ ์ฌ์ฌ์ฉํ ๊น?
- ์ฌ์ฌ์ฉํ ์ด์ ๋ ์ญ์ ๋ ๊ฒ์๊ธ์ ๋ํ ์ต์ํ์ ์ ๋ณด๋ฅผ ํฌํจํ ์๋ต์ ์ ๊ณต๋ฉฐ, ์ญ์ ๋ ๊ฒ์๊ธ์ ๋ํ ์ธ๋ถ ์ ๋ณด๋ฅผ ArticleResponse๋ก ๋ด์์ ํด๋ผ์ด์ธํธ๊ฐ ์ด๋ค ๊ฒ์๊ธ์ด ์ญ์ ๋์๋์ง ์ ์ ์๋๋ก ํ๋ ๊ฒ์ด ์ผ๊ด์ฑ ์๋ ์๋ต ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์ด๋ค.
๋ค๋ฅธ CRUD ์์ (์กฐํ, ์์ ๋ฑ)๊ณผ ์๋ต ํ์์ ํต์ผํ๋ ๊ฒ์ด API์ ์ ์ง๋ณด์์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๋ฆฌํ๋ค!
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/demo241106/articles")
public class ArticleController {
private final ArticleService articleService;
@GetMapping("") // ๋ค๊ฑด์กฐํ
public RsData<ArticlesResponse> list() {
List<ArticleDTO> articleDTOS = this.articleService.getList();
return RsData.of("200","๊ฒ์๊ธ ๋ค๊ฑด ์กฐํ ์ฑ๊ณต", new ArticlesResponse(articleDTOS));
}
@GetMapping("/{id}") //๋จ๊ฑด์กฐํ
public RsData<ArticleResponse> getArticle(@PathVariable("id") Long id){
Article article = this.articleService.getArticle(id);
if(article ==null)
return RsData.of("500","%d ๋ฒ๊ฒ์๋ฌผ์ ์กด์ฌํ์ง ์์ต๋๋ค.".formatted(id),null);
ArticleDTO articleDTO = new ArticleDTO(article);
return RsData.of("200","๊ฒ์๊ธ ๋จ๊ฑด ์กฐํ ์ฑ๊ณต", new ArticleResponse(articleDTO));
}
@PostMapping("") // ์์ฑ
public RsData<ArticleCreateResponse> create(@Valid @RequestBody ArticleCreateRequest articleCreateRequest){
Article article = this.articleService.write(articleCreateRequest.getSubject(),articleCreateRequest.getContent(),articleCreateRequest.getAuthor());
return RsData.of("200","๊ฒ์๊ธ ๋ฑ๋ก ์ฑ๊ณต", new ArticleCreateResponse(article));
}
@PatchMapping("/{id}") // ์์
public RsData<ArticleModifyResponse> modify(@PathVariable("id") Long id, @Valid @RequestBody ArticleModifyRequest articleModifyRequest){
Article article = this.articleService.getArticle(id);
if(article ==null)
return RsData.of("500","%d ๋ฒ๊ฒ์๋ฌผ์ ์กด์ฌํ์ง ์์ต๋๋ค.".formatted(id),null);
article = this.articleService.update(article,articleModifyRequest.getSubject(),articleModifyRequest.getContent(),articleModifyRequest.getAuthor());
return RsData.of("200","๊ฒ์๊ธ ์์ ์ฑ๊ณต",new ArticleModifyResponse(article));
}
@DeleteMapping("/{id}") // ์๊ฒ
public RsData<ArticleResponse> delete(@PathVariable("id") Long id){
Article article = this.articleService.getArticle(id);
if(article ==null)
return RsData.of("500","%d ๋ฒ๊ฒ์๋ฌผ์ ์กด์ฌํ์ง ์์ต๋๋ค.".formatted(id),null);
this.articleService.delete(article);
ArticleDTO articleDTO = new ArticleDTO(article);
return RsData.of("200","%d ๋ฒ ๊ฒ์๋ฌผ ์ญ์ ์ฑ๊ณต".formatted(id), new ArticleResponse(articleDTO));
}
}
๋ชจ๋ Service ๋ค์ ์ ์ฉ ์์ผฐ๋ค. ์ด์ PostMan ์์ ํ์ธํด๋ณด์
๊ฒ์๊ธ ์์ฑ

๋จ๊ฑด ์กฐํ

๋ค๊ฑด ์กฐํ

๊ฒ์๊ธ ์์

๊ฒ์๊ธ ์ญ์

์กฐํ, ์์ , ์ญ์ ์ค๋ฅ ๋ฉ์ธ์ง ์ถ๋ ฅ

RESTful API ์ค๊ณ
์ด๋ฒ ํ๋ก์ ํธ์์๋ ๊ฒ์๊ธ์ ๋ค๋ฃจ๋ API๋ฅผ ์ค๊ณํ๋ฉด์, HTTP ๋ฉ์๋(GET, POST, PATCH, DELETE)๋ฅผ ์ ์ ํ ์ฌ์ฉํ์ฌ RESTfulํ API๋ฅผ ๊ตฌํํ๋ค.
DTO(Data Transfer Object)์ Response ์ฒ๋ฆฌ
DTO๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ์ ๊ตฌ์กฐ๋ฅผ ๋ช
ํํ ๋ถ๋ฆฌํ๊ณ ์ด๋ฅผ ํตํด API์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฐฉ์์ด ๊น๋ํ๊ณ ์ ์ง๋ณด์ํ๊ธฐ ์ฌ์ด ๊ตฌ์กฐ๋ก ๊ด๋ฆฌํ๋ค.
๋ํ RsData ํด๋์ค๋ฅผ ๋์
ํ์ฌ API์ ๊ฒฐ๊ณผ๋ฅผ ํ์คํ ์ด๋ฅผ ํตํด ๊ฐ API ์๋ต์ ์ํ ์ฝ๋์ ๋ฉ์์ง๋ฅผ ์ผ๊ด๋๊ฒ ์ฒ๋ฆฌํ๊ณ , ํด๋ผ์ด์ธํธ๊ฐ API ์๋ต์ ์ฝ๊ฒ ํด์ํ ์ ์๋ค.
์ฝ๋์ ์ ์ง๋ณด์์ ํ์ฅ์ฑ
REST API๋ฅผ ์ค๊ณํ๋ฉด์ ๊ฐ ๊ธฐ๋ฅ๋ณ๋ก ์๋น์ค์ ์ปจํธ๋กค๋ฌ๋ฅผ ๋ช
ํํ ๋ถ๋ฆฌํ๊ณ , DTO์ ์๋ต ํด๋์ค๋ฅผ ํ์ฉํ์ฌ ์๋ต ํ์์ ์ผ๊ด์ฑ ์๊ฒ ์ฒ๋ฆฌํ๋ค. ์ด๋ฐ ๋ฐฉ์์ ํฅํ ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ๋ ์์ ์ ํ์ฅ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๋์ด๋ ๋ฐ ํฐ ๋์๋๋ค.