
질문 엔티티에 다대일 관계로 카테고리 엔티티를 추가하자. 아래와 같은 ERD가 그려질 것이다.
보다 간단한 방법으로 질문 엔티티에 카테고리 컬럼을 추가해도 상관없을 듯하다.

엔티티는 아래와 같이 작성
@Entity
@Getter
@Setter
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(unique = true)
private String name;
@OneToMany(mappedBy = "category")
private List<Question> questionList;
}
@Getter
@Setter
@Entity
public class Question {
...(생략)...
@ManyToOne
private Category category;
}
이제 게시물들을 카테고리 별로 조회해야 한다. QuestionService에서 사용하던 조회 조건을 조금 수정하면 쉽게 해결할 수 있다.
public class QuestionService {
private final QuestionRepository questionRepository;
private Specification<Question> search(String kw) {
return new Specification<>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<Question> q, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true); // 중복을 제거
Join<Question, SiteUser> u1 = q.join("author", JoinType.LEFT);
Join<Question, Answer> a = q.join("answerList", JoinType.LEFT);
Join<Answer, SiteUser> u2 = a.join("author", JoinType.LEFT);
return cb.or(cb.like(q.get("subject"), "%" + kw + "%"), // 제목
cb.like(q.get("content"), "%" + kw + "%"), // 내용
cb.like(u1.get("username"), "%" + kw + "%"), // 질문 작성자
cb.like(a.get("content"), "%" + kw + "%"), // 답변 내용
cb.like(u2.get("username"), "%" + kw + "%")); // 답변 작성자
}
};
}
public Page<Question> getList(int page, String kw) {
List<Sort.Order> sorts = new ArrayList<>();
sorts.add(Sort.Order.desc("createDate"));
Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
Specification<Question> spec = search(kw);
return this.questionRepository.findAll(spec, pageable);
}
(... 생략 ...)
}
public class QuestionService {
private final QuestionRepository questionRepository;
private Specification<Question> search(String kw, String categoryName) {
return new Specification<>() {
private static final long serialVersionUID = 1L;
@Override
public Predicate toPredicate(Root<Question> q, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true); // 중복을 제거
Join<Question, SiteUser> u1 = q.join("author", JoinType.LEFT);
Join<Question, Answer> a = q.join("answerList", JoinType.LEFT);
Join<Question, Category> c = q.join("category", JoinType.LEFT);
Join<Answer, SiteUser> u2 = a.join("author", JoinType.LEFT);
return cb.and(cb.or(cb.like(q.get("subject"), "%" + kw + "%"), // 제목
cb.like(q.get("content"), "%" + kw + "%"), // 내용
cb.like(u1.get("username"), "%" + kw + "%"), // 질문 작성자
cb.like(a.get("content"), "%" + kw + "%"), // 답변 내용
cb.like(u2.get("username"), "%" + kw + "%")), // 답변 작성자
// and
cb.like(c.get("name"), "%" + categoryName + "%")); // 카테고리 이름
}
};
}
public Page<Question> getList(int page, String kw, String categoryName) {
List<Sort.Order> sorts = new ArrayList<>();
sorts.add(Sort.Order.desc("createDate"));
Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
Specification<Question> spec = search(kw, categoryName);
return this.questionRepository.findAll(spec, pageable);
}
(... 생략 ...)
}
카테고리는 질문 게시판과 자유 게시판으로 나눴다.
@GetMapping("/question/list")@GetMapping("/freepost/list")질문 컨트롤러에 자유 게시판용 메서드를 추가하자. (질문 게시판용 메서드도 비슷하게 수정)
@GetMapping("/freepost/list")
public String freepostList(Model model, @RequestParam(value="page", defaultValue="0") int page,
@RequestParam(value = "kw", defaultValue = "") String kw) {
log.info("page:{}, kw:{}", page, kw);
Page<Question> paging = this.questionService.getList(page, kw, "자유");
model.addAttribute("paging", paging);
model.addAttribute("kw", kw);
return "question_list";
}
기존 레이아웃의 네비게이션 바 수정
<nav th:fragment="navbarFragment" class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="/">SBB</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
...(생략)...
<li>
<a class="nav-link" th:href="@{/question/list}">질문 게시판</a>
</li>
<li>
<a class="nav-link" th:href="@{/freepost/list}">자유 게시판</a>
</li>
</ul>
</div>
</div>
</nav>
게시물을 작성할 때도 카테고리를 선택해서 게시물을 생성해야 한다. 따라서 전체 카테고리 중에서 알맞는 카테고리를 선택할 수 있어야 한다. 즉, 아래와 같이 게시물 등록 폼에서 전체 카테고리를 조회한다.
@PreAuthorize("isAuthenticated()")
@GetMapping("/question/create")
public String questionCreate(Model model, QuestionForm questionForm) {
model.addAttribute("categoryList", categoryService.getList());
return "question_form";
}
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container">
<h5 class="my-3 border-bottom pb-2">게시물 등록</h5>
<form th:object="${questionForm}" method="post">
...(생략)...
<div class="mb-3">
<label for="category" class="form-label">카테고리</label>
<select class="form-select" th:field="*{category}">
<option th:each="category : ${categoryList}" th:value="${category.name}" th:text="${category.name}"></option>
</select>
</div>
<input type="submit" value="저장하기" class="btn btn-primary my-2">
</form>
</div>
</html>
게시물을 작성하면서 선택된 카테고리는 QuestionForm 클래스에 담겨서 컨트롤러에 넘어오게 수정한다.
@Getter
@Setter
public class QuestionForm {
@NotEmpty(message="제목은 필수항목입니다.")
@Size(max=200)
private String subject;
@NotEmpty(message="내용은 필수항목입니다.")
private String content;
@NotEmpty(message="카테고리는 필수항목입니다.")
private String category;
}
이제 컨트롤러에 넘어온 카테고리 이름으로 카테고리 엔티티를 조회하고, 조회한 카테고리 엔티티를 질문 엔티티에 넣어주고 저장하면 질문에 카테고리가 생기게 된다.
@PreAuthorize("isAuthenticated()")
@PostMapping("/question/create")
public String questionCreate(Model model, @Valid QuestionForm questionForm,
BindingResult bindingResult, Principal principal) {
if (bindingResult.hasErrors()) {
model.addAttribute("categoryList", categoryService.getList());
return "question_form";
}
SiteUser siteUser = this.userService.getUser(principal.getName());
Category category = this.categoryService.getCategory(questionForm.getCategory());
this.questionService.create(questionForm.getSubject(), questionForm.getContent(), siteUser, category);
return "redirect:/question/list";
}
좋은 글 감사합니다!! 근데 네비게이션 바 수정에서 자유게시판 href를 /question/freepost/list 로 해야할 거 같아요. 오타나신거 같은데 다른 분들 참고하세요