(15-1) 기능구현 - 글쓴이 표시 (엔티티 변경 - Author 속성 추가)
글쓴이 속성 추가하기
글쓴이 정보를 저장하는 기능을 구현하기 전에 엔티티를 변경한다.
시작하기 전에
개요 : 질문과 답변을 할 수 있는 게시판 서비스를 스프링부트를 통해 만들어 본다.
학습사이트 : https://wikidocs.net/book/7601
예제 코드 : https://github.com/pahkey/sbb
엔티티 변경
Question 엔티티 속성 추가
글쓴이 속성을 추가하자.
- Question.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/question/Question.java
package com.mysite.sbb.question;
(... 생략 ...)
import javax.persistence.ManyToOne;
import com.mysite.sbb.user.SiteUser;
(... 생략 ...)
@Getter
@Setter
@Entity
public class Question {
// 질문ID
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// 질문 제목
@Column(length = 200)
private String subject;
// 질문 내용
@Column(columnDefinition = "TEXT")
private String content;
// 질문 시간
private LocalDateTime createDate;
// 질문에 해당하는 답변
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<Answer> answerList;
// 질문 글쓴이
@ManyToOne
private SiteUser author;
}
여러개의 질문이 한 명의 사용자에게 작성될 수 있으므로 ManyToOne관계로 설정하였다.
Answer 엔티티 속성 추가
마찬가지로 글쓴이 속성을 추가해준다.
- Answer.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/Answer.java
package com.mysite.sbb.answer;
(... 생략 ...)
import com.mysite.sbb.user.SiteUser;
(... 생략 ...)
@Getter
@Setter
@Entity
public class Answer {
// 답변 id
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// 답변 내용
@Column(columnDefinition = "TEXT")
private String content;
// 답변 시각
private LocalDateTime createDate;
// 질문
@ManyToOne
private Question question;
@ManyToOne
// 답변 글쓴이
private SiteUser author;
}
- 테이블 확인
author_id 컬럼 생성!!
글쓴이 저장하기
만든 속성을 활용하자
답변에 작성자 저장하기
- AnswerController.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java
package com.mysite.sbb.answer;
import java.security.Principal;
(... 생략 ...)
@RequestMapping("/answer") // URL 프리픽스
@RequiredArgsConstructor
@Controller
public class AnswerController {
(... 생략 ...)
// post요청만 받아들일 경우에 사용하는 에너테이션
@PostMapping("/create/{id}") // (value=) 생략가능
public String createAnswer(Model model, @PathVariable("id") Integer id,
@Valid AnswerForm answerForm, BindingResult bindingResult, Principal principal) {
(... 생략 ...)
}
}
현재 로그인한 사용자에 대한 정보를 알기 위해서는 스프링 시큐리티가 제공하는 Principal 객체를 사용해야한다.
principal.getName() : 로그인한 사용자의 id 호출
- UserService.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/user/UserService.java
package com.mysite.sbb.user;
(... 생략 ...)
import java.util.Optional;
import com.mysite.sbb.DataNotFoundException;
(... 생략 ...)
@RequiredArgsConstructor
@Service
public class UserService {
(... 생략 ...)
// 사용자 조회 메서드
public SiteUser getUser(String username) {
// UserRepository - findByusername
Optional<SiteUser> siteUser = this.userRepository.findByusername(username);
if (siteUser.isPresent()) {
return siteUser.get();
}
// 조회 실패
else {
throw new DataNotFoundException("siteuser not found");
}
}
}
사용자명을 통해 SiteUser 객체를 조회할 수 있는 메서드 추가
- AnswerService.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerService.java
package com.mysite.sbb.answer;
(... 생략 ...)
import com.mysite.sbb.user.SiteUser;
(... 생략 ...)
@RequiredArgsConstructor
@Service
public class AnswerService {
private final AnswerRepository answerRepository;
// 답변 생성 메서드
// form 태그로부터 받은 question과 content 사용하여 객체를 생성하고 저장
// SiteUser 객체를 통해 글쓴이 저장
public void create(Question question, String content, SiteUser author) {
Answer answer = new Answer();
answer.setContent(content);
answer.setCreateDate(LocalDateTime.now());
answer.setQuestion(question);
answer.setAuthor(author);
this.answerRepository.save(answer);
}
}
답변 저장할때 조회한 SiteUser 객체도 저장하게끔 수정
- AnswerController.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java
package com.mysite.sbb.answer;
(... 생략 ...)
import com.mysite.sbb.user.SiteUser;
import com.mysite.sbb.user.UserService;
(... 생략 ...)
@RequestMapping("/answer") // URL 프리픽스
@RequiredArgsConstructor
@Controller
public class AnswerController {
// 변수 지정
private final QuestionService questionService;
private final AnswerService answerService;
private final UserService userService;
// post요청만 받아들일 경우에 사용하는 에너테이션
@PostMapping("/create/{id}") // (value=) 생략가능
public String createAnswer(Model model, @PathVariable("id") Integer id,
@Valid AnswerForm answerForm, BindingResult bindingResult, Principal principal) {
Question question = this.questionService.getQuestion(id);
// 글쓴이 속성
SiteUser siteUser = this.userService.getUser(principal.getName());
// 검증 실패시 다시 리턴
if (bindingResult.hasErrors()) {
model.addAttribute("question", question);
return "question_detail";
}
// 답변저장 - 답변 객체
this.answerService.create(question, answerForm.getContent(), siteUser);
return String.format("redirect:/question/detail/%s", id);
}
}
principal 객체를 통해 사용자명을 얻은 후에 사용자명을 통해 SiteUser객체를 얻어서 답변을 등록하는 AnswerService의 create 메서드에 전달
질문에 작성자 저장하기
답변에 글쓴이를 저장하는 방식과 똑같기때문에 패스!
참고(https://wikidocs.net/162330)
테스트 케이스 오류 해결
QuestionService의 create 메서드의 매개변수로 SiteUser가 추가되었기 때문에 이전에 작성한 테스트 케이스가 오류가 발생할 것이다. 테스트 케이스의 오류를 임시 해결하기 위해 다음과 같이 수정하자.
- SbbApplicationTests.java 수정
// 경로 : sbb/src/test/java/com/mysite/sbb/SbbApplicationTests.java
package com.mysite.sbb;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.mysite.sbb.question.QuestionService;
@SpringBootTest
class SbbApplicationTests {
@Autowired
private QuestionService questionService;
@Test
void testJpa() {
for (int i = 1; i<= 300 ; i++) {
String subject = String.format("테스트 데이터 [%03d] ", i);
String content = "내용";
this.questionService.create(subject, content,null);
}
}
}
문제점
로그인이 필요한 메서드
로그아웃 상태에서 질문 또는 답변을 등록하면 principal 객체가 null이기 때문에 500 오류가 발생한다!
principal 객체는로그인을 해야만 생성되는 객체이기 때문에 생기는 오류이다.
이를 수정해 보자!
- QuestionController.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/question/QuestionController.java
package com.mysite.sbb.question;
(... 생략 ...)
import org.springframework.security.access.prepost.PreAuthorize;
(... 생략 ...)
@RequiredArgsConstructor
@Controller
public class QuestionController {
(... 생략 ...)
@PreAuthorize("isAuthenticated()") // 로그인 필요
@GetMapping("/question/create")
public String questionCreate(QuestionForm questionForm) {
return "question_form";
}
// 질문 등록 저장하기
@PreAuthorize("isAuthenticated()") // 로그인 필요
@PostMapping("/question/create")
// 제목, 내용, 작성자를 파라미터로 받음
public String questionCreate(@Valid QuestionForm questionForm, BindingResult bindingResult, Principal principal) {
if (bindingResult.hasErrors()) {
return "question_form";
}
SiteUser siteUser = this.userService.getUser(principal.getName());
this.questionService.create(questionForm.getSubject(), questionForm.getContent(), siteUser);
return "redirect:/question/list";
}
}
@PreAuthorize(“isAuthenticated()”) 에너테이션이 붙은 메서드는 로그인이 필요한 메서드를 의미한다.
만약 로그아웃 상태에서 호출되면, 자동으로 로그인 페이지로 이동된다.
- AnswerController.java 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/answer/AnswerController.java
package com.mysite.sbb.answer;
(... 생략 ...)
import org.springframework.security.access.prepost.PreAuthorize;
(... 생략 ...)
@RequestMapping("/answer") // URL 프리픽스
@RequiredArgsConstructor
@Controller
public class AnswerController {
(... 생략 ...)
@PreAuthorize("isAuthenticated()")
@PostMapping("/create/{id}") // post요청만 받아들일 경우에 사용하는 에너테이션. (value=) 생략가능
public String createAnswer(Model model, @PathVariable("id") Integer id,
@Valid AnswerForm answerForm, BindingResult bindingResult, Principal principal) {
(... 생략 ...)
}
}
마찬가지로 @PreAuthorize(“isAuthenticated()”) 에너테이션을 활용해준다.
- SecurityConfig.java 수정
// 경로 sbb/src/main/java/com/mysite/sbb/SeurityConfig.java
package com.mysite.sbb;
(... 생략 ...)
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
(... 생략 ...)
@EnableGlobalMethodSecurity(prePostEnabled = true) // @PreAuthorize 애너테이션을 사용하기 위한 에너테이션
@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 자동 생성하는 에너테이션
@Configuration // 스프링의 환경설정 파일임을 의미하는 에너테이션
@EnableWebSecurity // 모든 요청URL이 스프링 시큐리티의 제어를 받도록 만드는 에너테이션
public class SecurityConfig {
(... 생략 ...)
}
disabled
이제 로그인하지 않은 상태에서 질문을 등록하거나, 답변을 등록하면 자동으로 로그인 화면으로 이동한다!!
하지만 생각해보면, 로그아웃 상태에서도 답변 작성은 할 수 있다.
큰 문제는 아니지만, 로그아웃 상태에서는 아예 답변도 작성하지 못하도록 수정하자.
- question_detail.html 수정
<!-- 경로 : sbb/src/main/resources/templates/question_detail.html -->
<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
(... 생략 ...)
<!-- 답변 반복 끝 -->
<!-- 답변 등록 from태그 : post방식 -->
<form th:action="@{|/answer/create/${question.id}|}" th:object="${answerForm}"
method="post" class="my-3">
<div th:replace="~{form_errors :: formErrorsFragment}"></div>
<textarea sec:authorize="isAnonymous()" disabled th:field="*{content}"
class="form-control" rows="10"></textarea>
<textarea sec:authorize="isAuthenticated()" th:field="*{content}"
class="form-control" rows="10"></textarea>
<input type="submit" value="답변등록" class="btn btn-primary my-2">
</form>
</div>
</html>
네비게이션바에서 사용했던 sec:authorize 속성을 사용하여 비로그인 답변을 막았다.
- SBB 테스트
답변 등록이 막혔다!
Leave a comment