점프투스프링부트 개선 | 프로필 + 조회수

Park JeaHyun·2023년 2월 16일

목표 1

JPQL

DB 조회 조건

  • 현재 사용자가 작성한 글, 답변, 댓글을 조회
    --> 글, 답변, 댓글 테이블과 유저 테이블을 조인
  • 가장 최근에 작성된 인스턴스?만 조회
    --> order by createDate & limit 기능 사용

구글링 해보니깐 JPQL에서 limit 기능은 Pageable 사용하라고 해서 아래와 같이 JPQL 작성
나머지 답변이나 댓글도 동일한 방식으로 작성 가능하므로 생략~

public interface QuestionRepository extends JpaRepository<Question, Integer> {
    
    ...(생략)...

    @Query("select q "
        + "from Question q "
        + "join SiteUser u on q.author=u "
        + "where u.username = :username "
        + "order by q.createDate desc ")
    List<Question> findCurrentQuestion(@Param("username") String username,
        Pageable pageable);
}

서비스 계층에서는 최근 몇개를 조회하고 싶은지 결정해서 Pageable 객체 생성

@RequiredArgsConstructor
@Service
public class QuestionService {

	...(생략)...
    
    public List<Question> getCurrentListByUser(String username, int num) {
        Pageable pageable = PageRequest.of(0, num);
        return questionRepository.findCurrentQuestion(username, pageable);
    }
}

프로필 화면은 로그인이 완료된 상태에서 보여질 수 있으므로 @PreAuthorize("isAuthenticated()") 추가

@RequiredArgsConstructor
@Controller
@RequestMapping("/user")
public class UserController {

    private final UserService userService;
    private final QuestionService questionService;
    private final AnswerService answerService;
    private final CommentService commentService;
    
    ...(생략)...
    
    @PreAuthorize("isAuthenticated()")
    @GetMapping("/profile")
    public String profile(Model model, Principal principal) {
        String username = principal.getName();
        model.addAttribute("username", username);
        model.addAttribute("questionList",
            questionService.getCurrentListByUser(username, 5));
        model.addAttribute("answerList",
            answerService.getCurrentListByUser(username, 5));
        model.addAttribute("commentList",
            commentService.getCurrentListByUser(username, 5));
        return "profile";
    }
}

간단하게 html 코드도 작성

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
    <section>
        <div class="row">
            <div class="col-lg-4">
                <div class="card mb-4">
                    <div class="card-body text-center">
                        <img src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-chat/ava3.webp"
                            alt="avatar" class="rounded-circle img-fluid" style="width: 150px;">
                        <h5 class="my-3" th:text="${username}"></h5>
                    </div>
                </div>
            </div>

            <div class="col-lg-8">
                <h5>최근 게시글</h5>
                <div class="card mb-4">
                    <div class="card-body">
                        <th:block th:each="question: ${questionList}">
                            <div class="row">
                                <div class="col-sm-3">
                                    <p class="mb-0" th:text="${question.subject}"></p>
                                </div>
                                <div class="col-sm-9">
                                    <p class="text-muted mb-0" th:text="${question.createDate}"></p>
                                </div>
                            </div>
                        </th:block>
                    </div>
                </div>
                <h5>최근 답변</h5>
                <div class="card mb-4">
                    <div class="card-body">
                        <th:block th:each="answer: ${answerList}">
                            <div class="row">
                                <div class="col-sm-3">
                                    <p class="mb-0" th:text="${answer.content}"></p>
                                </div>
                                <div class="col-sm-9">
                                    <p class="text-muted mb-0" th:text="${answer.createDate}"></p>
                                </div>
                            </div>
                        </th:block>
                    </div>
                </div>
                <h5>최근 댓글</h5>
                <div class="card mb-4">
                    <div class="card-body">
                        <th:block th:each="comment: ${commentList}">
                            <div class="row">
                                <div class="col-sm-3">
                                    <p class="mb-0" th:text="${comment.content}"></p>
                                </div>
                                <div class="col-sm-9">
                                    <p class="text-muted mb-0" th:text="${comment.createDate}"></p>
                                </div>
                            </div>
                        </th:block>
                    </div>
                </div>
            </div>
        </div>
    </section>
</div>
</div>
<script layout:fragment="script" type='text/javascript'>
</script>
</html>

목표 2


(최근 댓글, 답변은 그냥 skip... 크게 의미있진 않은듯?)

hit 컬럼 추가

@Getter
@Setter
@Entity
public class Question {

    ...(생략)...

    private Integer hit;
}

기존 테이블 수정하는 sql

alter table question 
add hit integer;

update question
set hit = 0
where hit is null;

로직 변경

기존 서비스 계층에서 게시글을 조회하는 메서드는 getQuestion 메서드가 있었다. 하지만 이 메서드는 게시글을 조회하는 것 뿐만 아니라 수정, 삭제 등 다른 곳에서도 사용되는 메서드이다. 이런 곳들에서는 조회수를 높이는 것이 아니라 단지 게시글을 불러오는 것만 필요하다. 그렇기 때문에 게시글을 조회할 때 조회수 + 1 로직을 수행하는 메서드를 따로 하나 추가했다.

@RequiredArgsConstructor
@Service
public class QuestionService {

	...(생략)...

    @Transactional
    public Question hitQuestion(Integer id) {
        Optional<Question> oquestion = this.questionRepository.findById(id);
        if (oquestion.isPresent()) {
            Question question = oquestion.get();
            question.setHit(question.getHit() + 1);
            return question;
        } else {
            throw new DataNotFoundException("question not found");
        }
    }
}
@Slf4j
@RequiredArgsConstructor
@Controller
public class QuestionController {

    private final QuestionService questionService;
    
    ...(생략)...
    
    @GetMapping(value = "/question/detail/{id}")
    public String detail(Model model, @PathVariable("id") Integer id,
        AnswerForm answerForm,
        @RequestParam(value = "answerPage",
            defaultValue = "0") int answerPage) {
        Question question = this.questionService.hitQuestion(id);
        Page<Answer> answerPaging =
            this.answerService.getList(question, answerPage);
        model.addAttribute("question", question);
        model.addAttribute("answerPaging", answerPaging);
        return "question_detail";
    }
    
    ...(생략)...
}

0개의 댓글