도메인 생성
Question.java
package login.login_spring.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Getter @Setter
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "question_id")
private Long id;
@Column(length = 200) //제목 최대 길이 200자
private String subject;
@Column(columnDefinition = "TEXT")
//텍스트를 열 데이터로 넣을 수 있음, 여러 줄의 텍스트를 쓸 수 있으며 글자 수 제한을 없애고 싶을 때 사용
private String content;
private LocalDateTime createDate;
private LocalDateTime modifyDate;
@ManyToOne //Many = author, 주인
@JoinColumn(name = "member_id") //join할 속성의 이름
private EzenMember author; //질문 작성자
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE) //질문 삭제 시 답변도 삭제
private List<Answer> answerList;
}
Answer.java
package login.login_spring.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Entity
@Getter @Setter
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "answer_id")
private Long id;
@Column(columnDefinition = "TEXT")
private String content;
private LocalDateTime createDate;
private LocalDateTime modifyDate;
@ManyToOne
@JoinColumn(name = "member_id")
private EzenMember author; //답변 작성자
@ManyToOne
@JoinColumn(name = "question_id")
private Question question;
}
EzenMember.java
package login.login_spring.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Entity
@Getter @Setter
public class EzenMember {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id") //속성 이름 지정
private Long id;
private String grade;
private String loginId;
private String name;
private String password;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL) //One = questionList, 종
// mappedBy = "주인"
private List<Question> questionList;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private List<Answer> answerList;
}
//List 자료형 : EzenMember는 여러 개의 Question과 Answer 객체를 리스트로 관리할 수 있음
//<Question>, <Answer> : 도메인 클래스
//CascadeType.ALL : EzenMember 삭제 시 연관된 객체 자동 삭제
생성된 테이블

리포지토리 생성
QuestionRepository.java
package login.login_spring.repository;
import login.login_spring.domain.Question;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface QuestionRepository extends JpaRepository<Question, Long> {
//제목으로 찾기
Question findBySubject(String subject);
//제목과 내용으로 찾기
Question findBySubjectAndContent(String subject, String content);
//질문엔터티의 subject 열 값들 중에 특정 문자열을 포함하는 데이터를 조회
List<Question> findBySubjectLike(String subject);
//findAll 메서드, questionList 페이징 처리
Page<Question> findAll(Pageable pageable);
}
AnswerRepository.java
package login.login_spring.repository;
import login.login_spring.domain.Answer;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AnswerRepository extends JpaRepository<Answer, Long> {
}
테스트 실행
QuestionTest.java
package login.login_spring;
import login.login_spring.domain.EzenMember;
import login.login_spring.domain.Question;
import login.login_spring.repository.EzenMemberRepository;
import login.login_spring.repository.QuestionRepository;
import net.bytebuddy.asm.Advice;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.Optional;
@SpringBootTest //SpringBoot 애플리케이션을 테스트할 때 사용
public class QuestionTest {
@Autowired private QuestionRepository questionRepository;
//Test는 @RequiredArgsConstructor를 생성 못하기 때문에 @Autowired로 직접 생성해야 함
//@Autowired 필드, 생성자, 메서드 자동 주입
@Autowired private EzenMemberRepository ezenMemberRepository;
@Test //Test 메서드 정의
void 질문() {
Question question = new Question();
question.setSubject("제목입니다");
question.setContent("내용입니다");
LocalDateTime now = LocalDateTime.now();
question.setCreateDate(now);
//question.setCreateDate(LocalDateTime.mow());
this.questionRepository.save(question);
}
@Test
void 대량삽입() {
Optional<EzenMember> member = ezenMemberRepository.findById(1L);
for(int i = 1; i <= 300; i++) {
Question question = new Question();
question.setSubject("제목" + i);
question.setContent("내용입니다");
question.setAuthor(member.get());
question.setCreateDate(LocalDateTime.now());
this.questionRepository.save(question);
}
}
}
테스트 결과

홈 화면 수정
loginHome.html
<html layout:decorate="~{layout}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<div class="container max600 vh-100 d-flex flex-column justify-content-center align-content-center" layout:fragment="content">
<div class="text-center">
<h2>홈 화면</h2>
</div>
<div class="py-3 row">
<p th:text="${loginMember.name} + '님, 환영합니다.'" class="mb-3 col-7 text-end"></p>
<form th:action="@{/logout}" method="post" class="col-5 text-start">
<button type="submit" class="btn btn-secondary btn-sm">로그아웃</button>
</form>
</div>
<div class="row">
<div class="col" th:if="${loginMember.grade == 'admin'}">
<button type="button" th:onclick="|location.href='@{/members}'|" class="w-100 btn btn-dark btn-lg">회원 목록</button>
</div>
<div class="col">
<button type="button" th:onclick="|location.href='@{/question/create}'|" class="w-100 btn btn-dark btn-lg">질문 등록하기</button>
</div>
<div class="col">
<button type="button" th:onclick="|location.href='@{/question/list}'|" class="w-100 btn btn-dark btn-lg">질문 목록</button>
</div>
</div>
</div>
</body>
</html>
DTO 생성
QuestionForm.java
package login.login_spring.dto;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import login.login_spring.domain.Answer;
import login.login_spring.domain.EzenMember;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Getter @Setter
public class QuestionForm {
private Long id;
@NotEmpty(message = "제목을 적어주세요")
@Size(max=200) //해당 필드 최대 200자까지 작성 가능
private String subject;
@NotEmpty(message = "내용을 적어주세요")
private String content;
}
AnswerForm.java
package login.login_spring.dto;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class AnswerForm {
@NotEmpty(message = "내용을 적어주세요")
private String content;
}
Q&A 페이지 생성
questionForm.html
<html layout:decorate="~{layout}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<div class="container vh-100 d-flex flex-column justify-content-center align-content-center" style="width: 800px;" layout:fragment="content">
<div class="bg-white border rounded-4 p-5">
<h1 class="pb-3">질문 등록</h1>
<form th:action th:object="${questionForm}" method="post">
<div th:if="${#fields.hasAnyErrors()}" class="alert alert-danger" role="alert">
<p th:each="err : ${#fields.allErrors()}" th:text="${err}" class="field-error2">전체 오류 메시지</p>
</div>
<div class="mb-3">
<label th:for="subject" class="form-label">제목</label>
<input type="text" class="form-control" th:field="*{subject}">
</div>
<div class="mb-3 pb-3">
<label th:for="content" class="form-label">내용</label>
<textarea class="form-control" th:field="*{content}" style="height: 200px;"></textarea>
</div>
<div class="row">
<div class="col">
<button type="submit" class="btn btn-primary btn-sm">저장하기</button>
</div>
</div>
</form>
</div>
</div>
</body>
</html>
questionList.html
<html layout:decorate="~{layout}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<div class="container content" style="width: 1000px;" layout:fragment="content">
<div class="bg-white border rounded-4 p-5">
<h1 class="pb-4">Q&A</h1>
<table class="table text-center">
<colgroup>
<col style="width: 10%;">
<col style="width: 50%;">
<col style="width: 10%;">
<col style="width: 30%;">
</colgroup>
<thead>
<tr class="table-primary">
<th scope="col">번호</th>
<th scope="col">제목</th>
<th scope="col">글쓴이</th>
<th scope="col">작성일시</th>
</tr>
</thead>
<tbody>
<tr class="align-middle" th:each = "question, iterStat : ${paging}">
<!-- 전체 게시글 번호를 계산 (내림차순):
(전체 게시글 수 - 현재 페이지 번호 * 페이지당 글 개수) - (현재 글의 index) -->
<th scope="row" th:text="${(paging.totalElements - (paging.number * paging.size) - iterStat.index)}"></th>
<!-- 순차적으로 번호 표시 -->
<td>
<a th:href="@{/questions/detail/{id} (id=${question.id})}" th:text="${question.subject}" class="questionTitle"></a>
<!-- 답글이 1개 이상 달리면 제목에 답글 개수 표시 -->
<span th:if="${#lists.size(question.answerList) != 0}" th:text="' [' + ${#lists.size(question.answerList)} + ']'"></span>
<!-- 오늘 작성된 글에만 'new' 배지 표시 -->
<span th:if="${#temporals.format(question.createDate, 'yyyy-MM-dd') == #temporals.format(#temporals.createNow(), 'yyyy-MM-dd')}" class="badge rounded-pill text-bg-danger">New</span>
</td>
<td th:text="${question.author != null ? question.author.name : 'Unknown'}"></td>
<td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
</tr>
</tbody>
</table>
<!-- 페이징 처리 시작 -->
<nav th:if="${!paging.isEmpty()}">
<ul class="pagination justify-content-center">
<li class="page-item"><a class="page-link" th:classappend="${!paging.hasPrevious}?'disabled'" th:href="@{|?page=${paging.number - 1}|}">Previous</a></li>
<li class="page-item" th:each="page : ${#numbers.sequence(0, paging.totalPages - 1)}" th:if="${page >= paging.number / 10 * 10 and page < (paging.number / 10 + 1) * 10}"><a class="page-link" th:href="@{|?page=${page}|}" th:text="${page + 1}" th:classappend="${page == paging.number}?'active'"></a></li>
<li class="page-item"><a class="page-link" th:classappend="${!paging.hasNext}?'disabled'" th:href="@{|?page=${paging.number+1}|}">Next</a></li>
</ul>
</nav>
<div>
<a th:href="@{/question/create}" role="button" class="btn btn-sm btn-secondary">질문 등록하기</a>
</div>
</div>
</div>
</body>
</html>
<!-- iterStat : Thymeleaf에서 인덱스와 관련된 정보를 제공 -->
<!-- #temporals.format : Thymeleaf에서 날짜 및 시간을 포맷팅할 때 사용하는 기능 -->
Q&A 상세보기 생성
questionDetail.html
<html layout:decorate="~{layout}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<div class="container content" style="width: 800px;" layout:fragment="content">
<div class="bg-white border rounded-4 p-5" th:object="${question}">
<h1 class="pb-3 text-center">질문 상세페이지</h1>
<div class="mb-3">
<h2 th:text="*{subject}"></h2>
</div>
<hr>
<div class="card mb-3">
<div class="card-body">
<p th:text="*{content}"></p>
<div class="d-flex" th:classappend="${loginMember.id == question.author.id ? 'justify-content-between' : 'justify-content-end'}">
<!-- 질문 수정, 삭제 버튼 -->
<div th:if="${loginMember.id == question.author.id}">
<a th:href="@{/questions/{id}/edit (id=${question.id})}" class="btn btn-outline-primary me-2">수정</a>
<button class="btn btn-outline-danger me-2" data-bs-toggle="modal" data-bs-target="#questionModal">삭제</button>
<!-- 질문 삭제 경고창 -->
<div class="modal fade" id="questionModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="questionModalLabel">경고</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
질문을 삭제하시겠습니까?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
<a class="btn btn-danger" th:href="@{/questions/{id}/delete (id=${question.id})}">삭제</a>
</div>
</div>
</div>
</div>
</div>
<div>
<div th:if="${question.modifyDate != null}" class="badge bg-light text-dark p-2 text-start">
<div class="mb-2">
<span th:text="${'수정한 날짜'}"></span>
</div>
<div th:text="${#temporals.format(question.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
<div class="badge bg-light text-dark p-2 text-start">
<div class="mb-2">
<span th:text="${question.author != null ? question.author.name : 'Unknown'}"></span>
</div>
<div th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-5">
<!-- 답변 개수 표시 -->
<h5 th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<hr>
<!-- 답변 목록 표시 -->
<div class="card mb-3" th:each = "answer : ${question.answerList}">
<!-- 답변 등록, 수정 시 답변 위치로 -->
<a th:id="|answer_${answer.id}|"></a>
<div class="card-body">
<p th:text="${answer.content}">답변</p>
<div class="d-flex" th:classappend="${loginMember.id == answer.author.id ? 'justify-content-between' : 'justify-content-end'}">
<!-- 답변 수정, 삭제 버튼 -->
<div th:if="${loginMember.id == answer.author.id}">
<a th:href="@{/answer/{id}/edit (id=${answer.id})}" class="btn btn-outline-primary me-2">수정</a>
<button class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#answerModal">삭제</button>
<!-- 답변 삭제 경고창 -->
<div class="modal fade" id="answerModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="answerModalLabel">경고</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
답변을 삭제하시겠습니까?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">닫기</button>
<a class="btn btn-danger" th:href="@{/answer/{id}/delete (id=${answer.id})}">삭제</a>
</div>
</div>
</div>
</div>
</div>
<div>
<div th:if="${answer.modifyDate != null}" class="badge bg-light text-dark p-2 text-start">
<div class="mb-2">
<span th:text="${'수정한 날짜'}"></span>
</div>
<div th:text="${#temporals.format(answer.modifyDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
<div class="badge bg-light text-dark p-2 text-start">
<div class="mb-2">
<span th:text="${answer.author != null ? answer.author.name : 'Unknown'}"></span>
</div>
<div th:text="${#temporals.format(answer.createDate, 'yyyy-MM-dd HH:mm')}"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 답변 등록 폼 -->
<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}" method="post">
<div th:if="${#fields.hasAnyErrors()}" class="alert alert-danger" role="alert">
<p th:each="err : ${#fields.allErrors()}" th:text="${err}" class="field-error2">전체 오류 메시지</p>
</div>
<div class="mb-2 pb-3">
<textarea class="form-control" th:field="*{content}" style="height: 200px;"></textarea>
</div>
<div>
<button type="submit" class="btn btn-primary">답변 등록</button>
<a th:onclick="|location.href='@{/question/list}'|" class="btn btn-secondary">닫기</a>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
Q&A 수정 페이지 생성
updateQuestionForm.html
<html layout:decorate="~{layout}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<div class="container vh-100 d-flex flex-column justify-content-center align-content-center" style="width: 800px;" layout:fragment="content">
<div class="bg-white border rounded-4 p-5">
<h1 class="pb-3">질문 수정</h1>
<form th:action th:object="${questionform}" method="post">
<div class="mb-3">
<label th:for="subject" class="form-label">제목</label>
<input type="text" class="form-control" th:errorClass="field-error" th:field="*{subject}">
<div th:errors="*{subject}" class="field-error"></div>
</div>
<div class="mb-3 pb-3">
<label th:for="content" class="form-label">내용</label>
<textarea class="form-control" th:errorClass="field-error" th:field="*{content}" style="height: 200px;"></textarea>
<div th:errors="*{content}" class="field-error"></div>
</div>
<div class="row">
<div class="col">
<button type="submit" class="w-100 btn btn-primary btn-lg">수정</button>
</div>
<div class="col">
<button type="button" th:onclick="|location.href='@{/questions/detail/{id} (id=${question.id})}'|" class="w-100 btn btn-secondary btn-lg">취소</button>
</div>
</div>
</form>
</div>
</div>
</body>
</html>
updateAnswerForm.html
<html layout:decorate="~{layout}" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<div class="container vh-100 d-flex flex-column justify-content-center align-content-center" style="width: 800px;" layout:fragment="content">
<div class="bg-white border rounded-4 p-5">
<h1 class="pb-3">답변 수정</h1>
<form th:action th:object="${answerForm}" method="post">
<div class="mb-3 pb-3">
<label th:for="content" class="form-label">내용</label>
<textarea class="form-control" th:errorClass="field-error" th:field="*{content}" style="height: 200px;"></textarea>
<div th:errors="*{content}" class="field-error"></div>
</div>
<div class="row">
<div class="col">
<button type="submit" class="w-100 btn btn-primary btn-lg">수정</button>
</div>
<div class="col">
<button type="button" th:onclick="|location.href='@{/questions/detail/{id} (id=${question.id})}'|" class="w-100 btn btn-secondary btn-lg">취소</button>
</div>
</div>
</form>
</div>
</div>
</body>
</html>
Q&A 서비스 생성
QuestionService.java
package login.login_spring.service;
import login.login_spring.domain.Question;
import login.login_spring.repository.QuestionRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class QuestionService {
private final QuestionRepository questionRepository;
//질문 등록
public void createQuestion(Question question) {
this.questionRepository.save(question);
}
//질문 전체 조회
public Page<Question> getList(int page) {
List<Sort.Order> sorts = new ArrayList<>();
sorts.add(Sort.Order.desc("createDate")); //날짜별로 내림차순 정렬
PageRequest pageable = PageRequest.of(page, 10, Sort.by(sorts));//한 페이지에 10개씩 보여줌
return questionRepository.findAll(pageable);
}
//질문 하나 조회
public Optional<Question> findOneQuestion(Long id) {
return questionRepository.findById(id);
}
//질문 수정
public Long updateQuestion(Question question) {
this.questionRepository.save(question);
return question.getId();
}
//질문 삭제
public void deleteQuestion(Question question) {
this.questionRepository.deleteById(question.getId());
}
}
AnswerService.java
package login.login_spring.service;
import login.login_spring.domain.Answer;
import login.login_spring.repository.AnswerRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class AnswerService {
private final AnswerRepository answerRepository;
//답변 등록
public void createAnswer(Answer answer){
this.answerRepository.save(answer);
}
//답변 하나 조회
public Optional<Answer> findOneAnswer(Long id) {
return answerRepository.findById(id);
}
//답변 수정
public Long updateAnswer(Answer answer) {
this.answerRepository.save(answer);
return answer.getId();
}
//답변 삭제
public void deleteAnswer(Answer answer) {
this.answerRepository.deleteById(answer.getId());
}
}
Q&A 컨트롤러 생성
QuestionController.java
package login.login_spring.controller;
import jakarta.validation.Valid;
import login.login_spring.domain.EzenMember;
import login.login_spring.domain.Question;
import login.login_spring.dto.AnswerForm;
import login.login_spring.dto.QuestionForm;
import login.login_spring.service.QuestionService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Optional;
@Controller
@RequiredArgsConstructor
public class QuestionController {
private final QuestionService questionService;
//질문 등록 폼 열기
@GetMapping("/question/create")
public String questionForm(@SessionAttribute(name = SessionConst.LOGIN_MEMBER) EzenMember loginMember, Model model) {
model.addAttribute("questionForm", new QuestionForm());
model.addAttribute("loginMember", loginMember);
return "user/questionForm";
}
//질문 등록
@PostMapping("/question/create")
public String createQuestion(@Valid @ModelAttribute("questionForm") QuestionForm form, BindingResult bindingResult, @SessionAttribute(name = SessionConst.LOGIN_MEMBER) EzenMember loginMember, Model model) {
if(bindingResult.hasErrors()){
model.addAttribute("loginMember", loginMember);
return "user/questionForm";
}
Question question = new Question();
question.setSubject(form.getSubject());
question.setContent(form.getContent());
question.setCreateDate(LocalDateTime.now());
question.setAuthor(loginMember);
questionService.createQuestion(question);
model.addAttribute("loginMember", loginMember);
return "redirect:/question/list";
}
//질문 목록 보기
@GetMapping("/question/list")
public String list(@SessionAttribute(name = SessionConst.LOGIN_MEMBER) EzenMember loginMember, @RequestParam(value = "page", defaultValue = "0") int page, Model model){
Page<Question> paging = questionService.getList(page);
model.addAttribute("paging", paging);
model.addAttribute("loginMember", loginMember);
return "user/questionList";
}
//질문 상세 보기
@GetMapping("/questions/detail/{id}")
public String detail(@SessionAttribute(name = SessionConst.LOGIN_MEMBER) EzenMember loginMember, @PathVariable("id") Long id, AnswerForm answerForm, Model model){
Optional<Question> question = questionService.findOneQuestion(id);
if(question.isPresent()){
Question questionGet = question.get();
model.addAttribute("question", questionGet);
model.addAttribute("loginMember", loginMember);
model.addAttribute("answerForm", answerForm); //answerForm 가져옴
return "user/questionDetail";
}
model.addAttribute("loginMember", loginMember);
return "user/questionList";
}
//질문 수정을 위해 작성된 질문 가져오기
@GetMapping("/questions/{id}/edit")
public String editQuestion(@SessionAttribute(name = SessionConst.LOGIN_MEMBER) EzenMember loginMember, QuestionForm form, @PathVariable("id") Long id, Model model) {
Optional<Question> findQuestion = questionService.findOneQuestion(id);
Question findQuestionGet = findQuestion.get();
form.setSubject(findQuestionGet.getSubject());
form.setContent(findQuestionGet.getContent());
model.addAttribute("questionform", form);
model.addAttribute("question", findQuestionGet);
model.addAttribute("loginMember", loginMember);
return "user/updateQuestionForm";
}
//질문 수정
@PostMapping("/questions/{id}/edit")
public String updateQuestion(@PathVariable("id") Long id, @SessionAttribute(name = SessionConst.LOGIN_MEMBER) EzenMember loginMember, @Valid @ModelAttribute("questionform") QuestionForm form, BindingResult bindingResult, Model model){
if(bindingResult.hasErrors()){
model.addAttribute("loginMember", loginMember);
return "user/updateQuestionForm";
}
Optional<Question> findQuestion = questionService.findOneQuestion(id);
Question question = new Question();
question.setId(id);
question.setAuthor(loginMember);
question.setSubject(form.getSubject());
question.setContent(form.getContent());
question.setCreateDate(findQuestion.get().getCreateDate());
question.setModifyDate(LocalDateTime.now());
questionService.updateQuestion(question);
model.addAttribute("loginMember", loginMember);
return String.format("redirect:/questions/detail/%s", id);
}
//질문 삭제
@GetMapping("/questions/{id}/delete")
public String deleteQuestion(@PathVariable("id") Long id){
Optional<Question> findQuestion = questionService.findOneQuestion(id);
if(findQuestion.isPresent()){
questionService.deleteQuestion(findQuestion.get());
}
return "redirect:/question/list";
}
}
AnwserController.java
package login.login_spring.controller;
import jakarta.validation.Valid;
import login.login_spring.domain.Answer;
import login.login_spring.domain.EzenMember;
import login.login_spring.domain.Question;
import login.login_spring.dto.AnswerForm;
import login.login_spring.service.AnswerService;
import login.login_spring.service.QuestionService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Optional;
@Controller
@RequiredArgsConstructor
public class AnswerController {
private final AnswerService answerService;
private final QuestionService questionService;
//질문에 대한 답변 쓰기
@PostMapping("/answer/create/{questionId}")
public String createAnswer(@PathVariable("questionId") Long questionId, @Valid AnswerForm answerForm, BindingResult bindingResult, @SessionAttribute(name = SessionConst.LOGIN_MEMBER) EzenMember loginMember, Model model){
Optional<Question> question = questionService.findOneQuestion(questionId);
if(bindingResult.hasErrors()){
model.addAttribute("question", question.get());
model.addAttribute("loginMember", loginMember);
return "user/questionDetail";
}
Answer answer = new Answer();
if(question.isPresent()){
answer.setContent(answerForm.getContent());
answer.setCreateDate(LocalDateTime.now());
answer.setQuestion(question.get());
answer.setAuthor(loginMember);
answerService.createAnswer(answer);
}
model.addAttribute("loginMember", loginMember);
return String.format("redirect:/questions/detail/%s#answer_%s", questionId, answer.getId());
}
//답변 수정을 위해 답변 가져오기
@GetMapping("/answer/{id}/edit")
public String editAnswer(@PathVariable("id") Long id, @SessionAttribute(name = SessionConst.LOGIN_MEMBER) EzenMember loginMember, AnswerForm answerForm, Model model){
Optional<Answer> findAnswer = answerService.findOneAnswer(id);
if(findAnswer.isPresent()){
Answer answer = findAnswer.get();
answerForm.setContent(answer.getContent());
model.addAttribute("loginMember", loginMember);
model.addAttribute("answerForm", answerForm);
model.addAttribute("question", answer.getQuestion());
return "user/updateAnswerForm";
}
return "redirect:/questions/list";
}
//답변 수정
@PostMapping("/answer/{id}/edit")
public String updateAnswer(@PathVariable("id") Long id, @Valid AnswerForm answerForm, BindingResult bindingResult, @SessionAttribute(name = SessionConst.LOGIN_MEMBER) EzenMember loginMember, Model model){
if(bindingResult.hasErrors()) {
model.addAttribute("loginMember", loginMember);
return "user/updateAnswerForm";
}
//답글 작성자의 아이디 조회
Optional<Answer> findAnswer = answerService.findOneAnswer(id);
Answer answer = findAnswer.get();
//답글에 해당하는 질문 작성자 아이디 조회
Long questionId = answer.getQuestion().getId();
answer.setContent(answerForm.getContent());
answer.setCreateDate(answer.getCreateDate());
answer.setModifyDate(LocalDateTime.now());
answerService.updateAnswer(answer);
model.addAttribute("loginMember", loginMember);
return String.format("redirect:/questions/detail/%s#answer_%s", questionId, answer.getId());
}
//답변 삭제
@GetMapping("/answer/{id}/delete")
public String deleteAnswer(@PathVariable("id") Long id) {
Optional<Answer> findAnswer = answerService.findOneAnswer(id);
if(findAnswer.isPresent()){
Answer answer = findAnswer.get();
Long questionId = answer.getQuestion().getId();
answerService.deleteAnswer(answer);
return String.format("redirect:/questions/detail/%s", questionId);
}
return "redirect:/question/list";
}
}
경로



'Framework > Spring' 카테고리의 다른 글
| [Spring] 검색 기능 예제 (0) | 2024.11.11 |
|---|---|
| [Spring] Thymeleaf - Paging (0) | 2024.11.06 |
| [Spring] 회원 가입과 로그인 예제 (1) | 2024.11.04 |
| [Spring] HTML 구성 요소의 통합 (2) | 2024.10.31 |
| [Spring] 회원 관리 예제 (1) | 2024.10.31 |