Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'jakarta.validation:jakarta.validation-api:3.0.2'
implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final'
implementation 'org.springframework.boot:spring-boot-starter-validation'


}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.request.ArticlesPatchRequestDto;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.request.ArticlesRequestDto;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.response.ArticlesResponseDto;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.entity.ArticlesEntity;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.repository.Articlesrepository;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.service.ArticlesService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/articles")
@RequiredArgsConstructor
public class ArticlesController {
private final ArticlesService articlesService;
private final Articlesrepository articlesrepository;

@GetMapping
public ResponseEntity<List<ArticlesResponseDto>> getAriticles() {
List<ArticlesResponseDto> articles = articlesService.getAll();
return ResponseEntity.ok(articles);
}
@GetMapping("/{id}")
public ResponseEntity<ArticlesResponseDto> getArticles(@PathVariable Long id) {
if(!articlesService.existsById(id)) {
return ResponseEntity.notFound().build();
}
ArticlesResponseDto articles = articlesService.getOne(id);
return ResponseEntity.ok(articles);
}
@PostMapping
public ResponseEntity<ArticlesResponseDto> createArticles(@Valid @RequestBody ArticlesRequestDto articles) {
if (!articlesService.checkRequest(articles))
//유효성 검사에 실패 했을때 (checkRequest)
return ResponseEntity.badRequest().build();
ArticlesResponseDto article = articlesService.saveArticle(articles);
if (article == null) {// DB에 저장이 실패할때
return ResponseEntity.internalServerError().build();
}
return ResponseEntity.status(201).body(article);
}
@PatchMapping("/{id}")
public ResponseEntity<ArticlesResponseDto> update(
@PathVariable Long id, @Valid @RequestBody ArticlesPatchRequestDto request) {
if(!articlesService.existsById(id)) {
return ResponseEntity.notFound().build();
}

if(!articlesService.hasValidPatchData(request)) {
return ResponseEntity.badRequest().build();
}
ArticlesResponseDto update = articlesService.updateArticle(id, request);
return ResponseEntity.ok(update);
}


@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteArticles(@PathVariable Long id) {
if(!articlesService.existsById(id)){
return ResponseEntity.notFound().build();
}
articlesService.deleteById(id);
return ResponseEntity.noContent().build();
}
Comment on lines +23 to +51

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

각 메서드 사이에 개행해주세요




}
Comment on lines +15 to +55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 유효성 검사,존재 확인 및 레포지터리(영속계층) 사용은 비즈니스로직에 해당하는 것 같습니다.책임분리에 대해 고민해보시고 이러한 로직은 서비스로 옮기는게 좋을 것 같습니다

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.request;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ArticlesPatchRequestDto {
@NotBlank(message = "제목은 비어 있을 수 없습니다.")
private String title;
@NotBlank(message = "내용은 비어 있을 수 없습니다.")
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.request;
import lombok.AllArgsConstructor;

import lombok.Getter;
import lombok.NoArgsConstructor;
import jakarta.validation.constraints.NotBlank;

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class ArticlesRequestDto {
@NotBlank(message = "제목은 비어 있을 수 없습니다.")
private String title;
@NotBlank(message = "내용은 비어 있을 수 없습니다.")
private String content;


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.request;

public @interface NotBlank {
String message();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 어노테이션을 정의하신 이유가 무엇인가요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 @notblank 어노테이션을 쓰다가 인텔리제이에서 자동으로 추가해준 것을 못 보고 넣은거 같습니다.. 수정하겠습니다

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.response;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ArticlesResponseDto {
private String title;
private String content;


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.gsm._8th.class4.backed.task._1._1.domain.ariticle.entity;

import com.gsm._8th.class4.backed.task._1._1.global.entity.BaseIdxEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Getter;

@Entity
@Table(name = "task.1-1")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테이블 명으로 이러한 명칭은 적절하지 않을 것 같습니다.
실제 리소스를 상징할 수 있는 명칭으로 해주세요

@Getter
public class ArticlesEntity extends BaseIdxEntity {
@Column(nullable = false)
private String title;

@Column(nullable = false , columnDefinition = "TEXT")
private String content;
public ArticlesEntity() {}
public ArticlesEntity( String title, String content) {
this.title = title;
this.content = content;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성자를 직접 정의하기 보단 Lombok에서 제공하는 생성자 생성 어노테이션들을 적용해주세요

public void update(String title, String content) {
if(title != null)
{
this.title = title;
}
if(content != null){
this.content = content;
}
}
Comment on lines +24 to +32

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update 메서드에서 이러한 null 검사를 하기보단 애초에 서비스 로직에서 null검사를 하는것이 더 깔끔하지 않을까요?


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.gsm._8th.class4.backed.task._1._1.domain.ariticle.repository;

import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.entity.ArticlesEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface Articlesrepository extends JpaRepository<ArticlesEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.gsm._8th.class4.backed.task._1._1.domain.ariticle.service;

import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.request.ArticlesPatchRequestDto;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.request.ArticlesRequestDto;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.response.ArticlesResponseDto;
import jakarta.validation.Valid;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public interface ArticlesService {
// 게시글 모든 글 조회
List<ArticlesResponseDto> getAll();
//단일 게시글 조회
ArticlesResponseDto getOne(Long id);
//게시글 생성
ArticlesResponseDto saveArticle(ArticlesRequestDto request);
//게시글 수정
ArticlesResponseDto updateArticle(Long id, @Valid ArticlesPatchRequestDto request);
//게시글 삭제
boolean deleteById(Long id);
//게시글 존재 여부 확인
boolean existsById(Long id);
//수정 요청 데이터 확인
boolean hasValidPatchData(@Valid ArticlesPatchRequestDto request);
//유효성 검사
boolean checkRequest(@Valid ArticlesRequestDto request);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.gsm._8th.class4.backed.task._1._1.domain.ariticle.service;

import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.request.ArticlesPatchRequestDto;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.request.ArticlesRequestDto;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.controller.response.ArticlesResponseDto;
import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.entity.ArticlesEntity;

import com.gsm._8th.class4.backed.task._1._1.domain.ariticle.repository.Articlesrepository;
import com.gsm._8th.class4.backed.task._1._1.global.entity.BaseIdxEntity;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

각 메서드에 트랜젝션을 별도로 선언하거나 하였다면 클래스단에서 선언하기 보단 메서드마다 알맞게 선언해주는게 좋을 것 같습니다

public class ArticlesServiceImpl implements ArticlesService {

private final Articlesrepository articlesrepository;
@Override
public List<ArticlesResponseDto> getAll(){
List<ArticlesEntity> entities = articlesrepository.findAll();
List<ArticlesResponseDto> dtos = entities.stream()
.map(entity -> convertToResponseDto(entity))
.collect(Collectors.toList());
Collections.reverse(dtos);
return dtos;


}
@Override
public ArticlesResponseDto getOne(Long id) {
Optional<ArticlesEntity> result = articlesrepository.findById(id);

if(result.isPresent()){
ArticlesEntity entity = result.get();
return convertToResponseDto(entity);
}
else {
return null;
}

}

@Override
@Transactional
public ArticlesResponseDto saveArticle(ArticlesRequestDto request) {
ArticlesEntity entity = new ArticlesEntity(request.getTitle(), request.getContent());
ArticlesEntity savedEntity = articlesrepository.save(entity);
return convertToResponseDto(savedEntity);
}

@Override
@Transactional
public ArticlesResponseDto updateArticle(Long id, ArticlesPatchRequestDto request) {
Optional<ArticlesEntity> updateresult = articlesrepository.findById(id);

if(updateresult.isEmpty()) { // 없으면 null 반환
return null;
}

ArticlesEntity entity = updateresult.get(); // 있을 때만 .get() 호출
entity.update(request.getTitle(), request.getContent());
ArticlesEntity savedEntity = articlesrepository.save(entity);
return convertToResponseDto(savedEntity);
}

@Override
@Transactional
public boolean deleteById(Long id) {
if(!articlesrepository.existsById(id)){
return false;
}
articlesrepository.deleteById(id);
return true;
}

@Override
public boolean existsById(Long id) {

return articlesrepository.existsById(id);
}

@Override
public boolean hasValidPatchData(ArticlesPatchRequestDto request) {
return existsTitle(request) || existsContent(request);
}

private boolean existsTitle(ArticlesPatchRequestDto request) { // 타입 변경
return request.getTitle() != null;
}

private boolean existsContent(ArticlesPatchRequestDto request) { // 타입 변경
return request.getContent() != null;
}
private ArticlesResponseDto convertToResponseDto(ArticlesEntity entity) {
return ArticlesResponseDto.builder()
.title(entity.getTitle())
.content(entity.getContent())
.build();
}
@Override
public boolean checkRequest(ArticlesRequestDto request) {
// title과 content가 모두 null이 아니고 비어있지 않은지 확인
return request.getTitle() != null && !request.getTitle().trim().isEmpty() &&
request.getContent() != null && !request.getContent().trim().isEmpty();
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import jakarta.persistence.*;
import lombok.Getter;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Getter
@MappedSuperclass
abstract class BaseIdxEntity {
@Component
public abstract class BaseIdxEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "idx", nullable = false, updatable = false, insertable = false, unique = true)
Expand Down