elevne's Study Note
Spring Boot 복습 (4) 본문
저번 시간에 이어 이번에는 Bootstrap 을 이용하여 페이지를 꾸미고, 새로운 질문을 등록하는 기능을 개발하였다. 질문을 등록하는 것은 PostMapping 을 이용하여 Form 을 전달받게 되는데, 질문이나 내용을 등록할 때 비어있는 값으로 등록할 수 없도록 처리를 해줘야 한다. 이러한 Validation 을 진행하기 위해서 Spring Boot Validation 라이브러리가 사용된다. 우선 build.gradle 의 dependencies 에 implementation 'org.springframework.boot:spring-boot-starter-validation' 를 추가해준다. 해당 라이브러리를 추가해주면 아래와 같은 어노테이션들을 사용하여 입력 값들을 검증해볼 수 있다.
또, 위 어노테이션들을 사용하여 검증하기 위해서는 Form Class 가 필요하다. Subject, Content 에 대응하는 QuestionForm 클래스를 아래와 같이 작성해주었다.
package com.springboot.study.form;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
@Data
public class QuestionForm {
@NotEmpty(message = "제목은 필수사항입니다.")
@Size(max=200)
private String subject;
@NotEmpty(message = "내용은 필수사항입니다.")
private String content;
}
작성한 QuestionForm 클래스를 사용하기 위해서는 컨트롤러를 아래와 같이 수정해야 한다.
@PostMapping("/create")
public String questionCreate(@Valid QuestionForm questionForm, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "question_form";
}
this.questionService.create(questionForm.getSubject(), questionForm.getContent());
return "redirect:/question/list";
}
questionCreate 메서드의 매개변수를 subject, content 대신에 QuestionForm 객체로 변경했다. subject, content 항목을 지닌 Form 이 전송되면 QuestionForm 의 subject, content 속성이 자동으로 Binding 되는 것이다. (Spring Framework 의 Binding 기능) @Valid 어노테이션은 QuestionForm 의 @NotEmpty, @Size 등으로 설정한 검증 기능을 동작하게 해준다. 이어지는 BindingResult 매개변수는 @Valid 어노테이션으로 인해 검증이 수행된 결과를 의미하는 객체이다. BindingResult 매개변수는 항상 @Valid 매개변수 바로 뒤에 위치해야 한다. 만약 2 개의 매개변수의 위치가 정확하지 않다면 @Valid 만 적용이 되어 입력값 검증 실패 시 400 오류가 발생한다. questionCreate 메서드는 bindingResult.hasErrors() 를 호출하여 오류가 있는 경우에는 다시 폼을 작성하는 화면을 렌더링하게끔 하는 것이다.
답변 등록 Controller 메서드도 아래와 같이 수정해준다.
@PostMapping("/create/{id}")
public String createAnswer(Model model, @PathVariable("id") Integer id,
@Valid AnswerForm answerForm, BindingResult bindingResult) {
Question question = this.questionService.getQuestion(id);
if (bindingResult.hasErrors()) {
model.addAttribute("question", question);
return "question_detail";
}
this.answerService.create(question, answerForm.getContent());
return String.format("redirect:/question/detail/%s", id);
}
그 다음으로는 게시판 글들을 페이지네이션 처리하는 해주어야 했다. 우선 아래와 같은 테스트 코드로 300 개의 더미데이터를 생성해주었다.
@Test
void testJpa() {
for (int i = 1; i <= 300; i++) {
String subject = String.format("테스트 데이터입니다:[%03d]", i);
String content = "내용무";
this.questionService.create(subject, content);
}
}
Question Repository 에 Page<Question> findAll(Pageable pageable); 메서드를 추가한다. Pageable 객체를 입력으로 받아 Page<Question> 타입 객체를 리턴하는 메서드이다. Question Service 도 아래와 같이 수정해준다.
public Page<Question> getList(int page){
Pageable pageable = PageRequest.of(page, 10);
return this.questionRepository.findAll(pageable);
}
getList 메서드는 이제 정수 타입의 페이지 번호를 입력받아 해당 페이지의 질문 목록을 리턴하는 메서드이다. Pageable 객체를 생성할 때 사용한 PageRequest.of(page, 10) 에서 page 는 조회할 페이지의 번호이고 10 은 한 페이지에 보여줄 게시물의 갯수이다. 이를 수정하였으니 Question Controller 도 아래와 같이 수정해줄 필요가 있다.
@GetMapping("/list")
public String list(Model model, @RequestParam(value="page", defaultValue = "0") int page){
Page<Question> paging = this.questionService.getList(page);
model.addAttribute("paging", paging);
return "question_list";
}
위에서 사용되는 Page 객체에는 아래와 같은 속성들이 들어있다.
지금까지의 코드만으로는 우선 10 개 씩 분할되서 나오는 것을 확인할 수 있지만, 다음 페이지로 넘어갈 수가 없다. question_list.html 파일에 아래와 같이 작성해준다.
<!-- 페이징처리 시작 -->
<div th:if="${!paging.isEmpty()}">
<ul class="pagination justify-content-center">
<li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
<a class="page-link"
th:href="@{|?page=${paging.number-1}|}">
<span>이전</span>
</a>
</li>
<li th:each="page: ${#numbers.sequence(0, paging.totalPages-1)}"
th:if="${page >= paging.number-5 and page <= paging.number+5}"
th:classappend="${page == paging.number} ? 'active'"
class="page-item">
<a th:text="${page}" class="page-link" th:href="@{|?page=${page}|}"></a>
</li>
<li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
<a class="page-link" th:href="@{|?page=${paging.number+1}|}">
<span>다음</span>
</a>
</li>
</ul>
</div>
<!-- 페이징처리 끝 -->
또, 현재 질문은 등록한 순서대로 데이터가 표시되는데 게시판은 가장 최근에 작성한 게시물이 먼저 보이는 것이 일반적이다. 이를 구현하기 위해 Question Service 를 아래와 같이 수정해준다.
public Page<Question> getList(int page){
List<Sort.Order> sorts = new ArrayList<Sort.Order>();
sorts.add(Sort.Order.desc("createdDate"));
Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
return this.questionRepository.findAll(pageable);
}
게시물을 역순으로 조회하기 위해서는 위와 같이 PageRequest.of 메서드의 세 번째 파라미터로 Sort 객체를 전달해야 한다. Sort.Order 객체로 구성된 리스트에 Sort.Order 객체를 추가하고 Sort.by(SortList) 로 Sort 객체를 생성할 수 있다. 결과를 확인해보면 페이지네이션, 페이지 이동 버튼 그리고 정렬이 전부 잘 되어있는 것을 확인할 수 있다.
Reference:
'Backend > Spring' 카테고리의 다른 글
Spring Boot 복습 (6) (0) | 2023.04.10 |
---|---|
Spring Boot 복습 (5) (0) | 2023.04.09 |
Spring Boot 복습 (3) (0) | 2023.04.07 |
Spring Boot 복습 (2) (0) | 2023.04.05 |
Spring Boot 복습 (1) (0) | 2023.04.04 |