
1. 게시글 작성/수정 기능
2. 특정 게시글 조회 기능
3. 특정 게시글 삭제 기능
4. 게시글 검색 기능
jakarta.validation 패키지에서 제공하는@Valid : DTO 내부 필드의 유효성 검사 수행@RequestBody)@Validated : @RequestParam, PathVariable 등 단일 값 검증 @NotNull : null ❌,@NotEmpty : null 또는 "" ❌@NotBlank : null 또는 "" 또는 " " ❌ - 공백을 허용하지 않음 💡 @NotBlank는 @NotEmpty보다 더 엄격하다.
@Size(min, max) : 문자열 길이 제한@Pattern(regexp) : 정규식으로 검증💡 @Pattern을 이용하면 특정 패턴(이메일, 전화번호) 검증이 가능하다.
@Min(value) : 최소값 제한 - int, long에만 적용@Max(value) : 최대값 제한@Positive : 양수만 허용 - 모든 숫자 타입 지원@PostiveOrZero : 0 또는 양수 허용@Negative : 음수만 허용@NegativeOrZero : 0 또는 음수 허용@Digits(integer, fraction) : 정수, 소수 자리수 제한게시글 수정 기능의 유효성 검사는 게시글 작성과 동일하므로 생략
📂 PostCreateRequest.java - 게시글 작성 DTO
@NotBlank, @Size(min, max) 검증 어노테이션 적용@NotBlank는 공백을 허용하지 않으므로, 요구사항을 만족하기 위해 적용@Builder
public record PostCreateRequest(
@NotBlank(message = "제목은 필수값입니다.")
@Size(min = 1, max = 15, message = "제목은 1글자 이상 15자 이하로 작성해주세요.")
String title,
@NotBlank(message = "내용은 필수값입니다.")
@Size(min = 1, max = 1000, message = "내용은 1글자 이상 1000글자 이하로 작성해주세요.")
String content
) {
}
📂 PostController.java
@Valid를 적용@Valid를 사용하면 DTO 내부 필드의 유효성 검사를 수행한다.💡 @RequestBody가 JSON 데이터를 PostCreateRequest객체로 변환한 후, 해당 객체에 대한 유효성 검사 수행
@PostMapping("/post")
public ResponseEntity<PostResponse> savePost(@RequestBody @Valid PostCreateRequest request){
return postService.savePost(request);
}
요구사항 중 게시글 검색 기능에 대해
"검색 키워드는 공백을 제외한 1글자 이상이어야 한다"는 어떻게 검사해야될지 난감했다.
왜냐 ! ! ! 나는 지금까지 유효성 검사를 DTO 검증 어노테이션 적용 + 컨트롤러에 @Valid 했기 때문 ! !
그래서 이 부분에서 다시 한번 검색 키워드 기반 검색 기능의 리팩토링 필요성을 절실히 느꼈다 ..
쨋든 구현은 해야되니까, 얼레벌레 검색해서
컨트롤러 단에서 @Validated와 @NotBlank 사용해 검색 키워드 유효성 검사를 구현했다.
📂 PostController.java
@Validated 어노테이션을 적용하고, 메서드 레벨에서 @NotBlank를 적용했다. @Validated // 검색 키워드 유효성 검사를 위해 추가
public class PostController {
private final PostService postService;
... 생략
// 게시글 검색 - 검색키워드가 포함된 게시글 검색
@GetMapping("/post/search")
public PagedModel<PostResponse> getPostByKeyword(@RequestParam @NotBlank(message = "검색 키워드는 공백 제외 1글자 이상 입력해주세요.") String keyword,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "15") int size){
if(size > 100){ size = 100; }
Pageable pageable = PageRequest.of(page-1, size, Sort.by("createdAt").descending());
return postService.getPostByKeyword(keyword, pageable);
}
... 생략
}
@Validated는 Spring에서 유효성 검사를 활성화하는 어노테이션으로,@RequestParam, @PathVariable 등의 개별 요청 값(단일 값)을 검증할 때 사용한다.@Validated를 컨트롤러 클래스에 붙여야 개별 요청 파라미터의 유효성 검사가 동작한다.💡@Valid는 @RequestBody(DTO)에서만 작동한다.
@RequestParam으로 받은 keyword를 컨트롤러에서 trim(), isEmpty()와 같은 메서드를 사용해 @GetMapping("/post/search")
public PagedModel<PostResponse> getPostByKeyword(@RequestParam String keyword,
@RequestParam(name = "page", defaultValue = "1") int page,
@RequestParam(name = "size", defaultValue = "15") int size){
if(keyword.trim().isEmpty()){
throw new Exception("검색 키워드는 공백 제외 1글자 이상 입력해주세요.")
}
... 생략
return postService.getPostByKeyword(keyword, pageable);
}
쨋든 .. 결론은 검색 기능 리팩토링 해야 한다.
예외 처리 구현은 비교적 수월했는데, 그 이유는
특정 게시물을 조회할 때나 삭제할 때나 둘 다 엔티티를 조회해오는 과정에서 발생하는 문제이기 때문에
레포지토리에서 예외 처리를 하면 된다.
💡JpaRepository에서 findById()의 반환타입이 Optinal<>임을 기억하자
📂 PostRepository.java
findOne()를 default 메서드로 정의해,id의 엔티티(게시글)가 존재하지 않는 경우 예외를 던지도록 처리한다,@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
default Post findOne(Long id){
return findById(id)
.orElseThrow(() -> new IllegalArgumentException("게시글이 존재하지 않습니다!"));
}
... 생략
}
과연 내가 한게 예외 처리가 맞나.. ? 라는 생각이 들었다.
요구사항은 "존재하지 않는 게시글일 경우 에러 메세지로 응답"인데,
내가 한건 그냥 예외를 발생시키는 코드다 ... ㅎ ㅎ ㅎ ㅎ ㅎ ㅎㅎㅎ ;;;;; ;;
@ControllerAdvice, @ExceptionHandler, @ResponseStatus왜 안썼니 .. . .?
넌 알고 있어 의연.. 할 수 있어 유 캔 두 잇, 아이 캔 두 잇 ..
📂 Example.java
ErrorResponse DTO까지 정의하면 금상첨화다..@RestControllerAdvice
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(EntityNotFoundException.class)
public Map<String, String> handleEntityNotFoundException(EntityNotFoundException ex) {
Map<String, String> error = new HashMap<>();
error.put("error", ex.getMessage());
return error;
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public Map<String, String> handleIllegalArgumentException(IllegalArgumentException ex) {
Map<String, String> error = new HashMap<>();
error.put("error", ex.getMessage());
return error;
}
}