이전글 보기

https://aamoos.tistory.com/674

 

[Spring Jpa] 5. 게시판 만들기 - 게시판 등록, 수정 테스트 파일 만들기

이전글 보기 https://aamoos.tistory.com/671 [Spring Jpa] 4. 게시판 만들기 - Entity 생성, 빌드패턴, P6 spy 설정 이전글 보기 https://aamoos.tistory.com/670 [Spring Jpa] 3. 게시판 만들기 - H2 Database..

aamoos.tistory.com

 

목표

- querydsl을 적용해보고 페이징을 구현해보겠습니다.

 

QueryDsl 적용

https://aamoos.tistory.com/454

 

[SpringBoot] Querydsl 적용

build.gradle plugins { id 'org.springframework.boot' version '2.5.3' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" // (1) id '..

aamoos.tistory.com

 

게시판 페이징 목록 구현

 

BoardController.java


package jpa.board.controller;

import jpa.board.dto.BoardDto;
import jpa.board.repository.CustomBoardRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
/**
 * packageName    : jpa.board.controller
 * fileName       : BoardController
 * author         : 김재성
 * date           : 2022-08-01
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-01        김재성       최초 생성
 */
@Controller
@RequiredArgsConstructor
public class BoardController {

    private final CustomBoardRepository customBoardRepository;

    /**
    * @methodName : list
    * @date : 2022-08-02 오후 2:07
    * @author : 김재성
    * @Description: 게시판 목록화면
    **/
    @GetMapping("/")
    public String list(String searchVal, Pageable pageable, Model model){
        Page<BoardDto> results = customBoardRepository.selectBoardList(searchVal, pageable);
        model.addAttribute("list", results);
        model.addAttribute("maxPage", 5);
        pageModelPut(results, model);
        return "board/list";
    }

    private void pageModelPut(Page<BoardDto> results, Model model){
        model.addAttribute("totalCount", results.getTotalElements());
        model.addAttribute("size",  results.getPageable().getPageSize());
        model.addAttribute("number",  results.getPageable().getPageNumber());
    }

    /**
    * @methodName : write
    * @date : 2022-08-02 오후 2:07
    * @author : 김재성
    * @Description: 게시판 글쓰기화면
    **/
    @GetMapping("/write")
    public String write(){
        return "board/write";
    }

    /**
    * @methodName : update
    * @date : 2022-08-02 오후 2:07
    * @author : 김재성
    * @Description: 게시판 수정화면
    **/
    @GetMapping("/update")
    public String update(){
        return "board/update";
    }
}

 

- 게시판 목록화면 부분을 해당 코드로 변경합니다.

 

dto 패키지를 생성하고 BoardDto 파일을 하나 만듭니다.

 

BoardDto.java

package jpa.board.dto;

import com.querydsl.core.annotations.QueryProjection;
import jpa.board.entity.Board;
import jpa.board.entity.Member;
import lombok.Data;

import javax.validation.constraints.NotEmpty;
import java.time.LocalDateTime;

/**
 * packageName    : jpa.board.dto
 * fileName       : BoardDto
 * author         : 김재성
 * date           : 2022-08-02
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-02        김재성       최초 생성
 */

@Data
public class BoardDto {

    private Long id;            //시퀀스

    @NotEmpty(message = "제목은 필수입니다.")
    private String title;              //제목
    private String content;            //내용
    private LocalDateTime regDate;     //등록 날짜
    private LocalDateTime uptDate;     //수정 날짜
    private Long viewCount;            //조회수
    private String username;            //사용자 이름

    public BoardDto(){

    }

    public BoardDto(String title, String content){
        this.title = title;
        this.content = content;
    }

    @QueryProjection
    public BoardDto(Long id, String title, String content, LocalDateTime regDate , LocalDateTime uptDate, Long viewCount, String username){
        this.id = id;
        this.title = title;
        this.content = content;
        this.regDate = regDate;
        this.uptDate = uptDate;
        this.viewCount = viewCount;
        this.username = username;
    }

    public Board toEntity(Member member){
        return Board.builder()
                .member(member)
                .title(title)
                .content(content)
                .build();
    }
}

@QueryProejction이란?

 - 간단하게 말해 Select 대상을 지정하는것입니다. DTO를 대상으로 하는게 아니라 DTO 기반으로 생성된 QDTO 객체를 대상으로 합니다.

 

repository 패키지 안에 CustomBoardRepository를 하나 생성합니다.

 

CustomBoardRepository


package jpa.board.repository;

import jpa.board.dto.BoardDto;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

/**
 * packageName    : jpa.board.repository
 * fileName       : CustomBoardRepository
 * author         : 김재성
 * date           : 2022-08-02
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-02        김재성       최초 생성
 */
public interface CustomBoardRepository {
    Page<BoardDto> selectBoardList(String searchVal, Pageable pageable);
}

 

repositoryImpl 패키지를 하나 만들고 BoardRepositoryImpl.java 파일을 생성합니다.

 

BoardRepositoryImpl.java


package jpa.board.repositoryImpl;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jpa.board.dto.BoardDto;
import jpa.board.dto.QBoardDto;
import jpa.board.repository.CustomBoardRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.List;
import static jpa.board.entity.QBoard.board;
import static jpa.board.entity.QMember.member;

/**
 * packageName    : jpa.board.repositoryImpl
 * fileName       : BoardRepositoryImpl
 * author         : 김재성
 * date           : 2022-08-02
 * description    :
 * ===========================================================
 * DATE              AUTHOR             NOTE
 * -----------------------------------------------------------
 * 2022-08-02        김재성       최초 생성
 */
@Repository
public class BoardRepositoryImpl implements CustomBoardRepository {
    private final JPAQueryFactory jpaQueryFactory;

    public BoardRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
        this.jpaQueryFactory = jpaQueryFactory;
    }

    @Override
    public Page<BoardDto> selectBoardList(String searchVal, Pageable pageable) {
        List<BoardDto> content = getBoardMemberDtos(searchVal, pageable);
        Long count = getCount(searchVal);
        return new PageImpl<>(content, pageable, count);
    }

    private Long getCount(String searchVal){
        Long count = jpaQueryFactory
                .select(board.count())
                .from(board)
                //.leftJoin(board.member, member)   //검색조건 최적화
                .fetchOne();
        return count;
    }

    private List<BoardDto> getBoardMemberDtos(String searchVal, Pageable pageable){
        List<BoardDto> content = jpaQueryFactory
                .select(new QBoardDto(
                         board.id
                        ,board.title
                        ,board.content
                        ,board.regDate
                        ,board.uptDate
                        ,board.viewCount
                        ,member.username))
                .from(board)
                .leftJoin(board.member, member)
                .orderBy(board.id.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();
        return content;
    }
}
- 지금은 검색 조건을 제외한 paging만 처리하였습니다. 게시판 시퀀스가 가장 최신이 위로 가게 조회가 됩니다.

 

 

게시판 페이징 목록 html

list.html


<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"  xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layout/default_layout">
    <div layout:fragment="content" class="content">
    <nav class="container">
        <br>
            <div class="input-group">
                <input type="text" class="form-control" placeholder="제목을 입력해주세요.">
                <button type="submit" class="btn btn-secondary">검색</button>
            </div>
        <br>
        <form>
            <table class="table table-hover">
                <colgroup>
                    <col width="2%" />
                    <col width="5%" />
                    <col width="20%" />
                    <col width="5%" />
                    <col width="5%" />
                    <col width="5%" />
                </colgroup>
                <thead>
                    <tr>
                        <th>
                            <label class="checkbox-inline">
                            <input type="checkbox" id="allCheckBox" class="chk">
                            </label>
                        </th>
                        <th>번호</th>
                        <th>제목</th>
                        <th>작성자</th>
                        <th>날짜</th>
                        <th>조회수</th>
                    </tr>
                </thead>

                <tbody>
                    <tr th:each="list, index : ${list}">
                        <td>
                            <label class="checkbox-inline">
                                <input type="checkbox" class="chk" name="cchk" value="">
                            </label>
                        <td th:text="${totalCount - (size * number) - index.index}"></td>
                        <td><a th:text="${list.title}" href=""></a></td>
                        <td th:text="${list.username}"></td>
                        <td th:text="${#temporals.format(list.regDate, 'yyyy-MM-dd')}"></td>
                        <td th:text="${list.viewCount}"></td>
                    </tr>
                </tbody>
            </table>
            <br>

            <div class="d-flex justify-content-end">
                <a class="btn btn-danger">글삭제</a>
                <a href="/write" class="btn btn-primary">글쓰기</a>
            </div>
            <br>
            <nav class="container d-flex align-items-center justify-content-center" aria-label="Page navigation example"
                 th:with="start=${(list.number/maxPage)*maxPage + 1},
                  end=(${(list.totalPages == 0) ? 1 : (start + (maxPage - 1) < list.totalPages ? start + (maxPage - 1) : list.totalPages)})">
                <ul class="pagination">

                    <li th:if="${start > 1}" class="page-item">
                        <a th:href="@{/?(page=0)}" class="page-link" href="#" aria-label="Previous">
                            <span aria-hidden="true">&laquo;&laquo;</span>
                        </a>
                    </li>

                    <li th:if="${start > 1}" class="page-item">
                        <a th:href="@{/?(page=${start - maxPage-1})}" class="page-link" href="#" aria-label="Previous">
                            <span aria-hidden="true">&laquo;</span>
                        </a>
                    </li>

                    <li th:each="page: ${#numbers.sequence(start, end)}" class="page-item" th:classappend="${list.number+1 == page} ? active">
                        <a th:href="@{/?(page=${page-1})}" th:text="${page}" class="page-link" href="#">1</a>
                    </li>

                    <li th:if="${end < list.totalPages}" class="page-item">
                        <a th:href="@{/?(page=${start + maxPage -1})}" class="page-link" href="#" aria-label="Next">
                            <span aria-hidden="true">&raquo;</span>
                        </a>
                    </li>

                    <li th:if="${end < list.totalPages}" class="page-item">
                        <a th:href="@{/?(page=${list.totalPages-1})}" class="page-link" href="#" aria-label="Next">
                            <span aria-hidden="true">&raquo;&raquo;</span>
                        </a>
                    </li>
                </ul>
            </nav>
        </form>
    </nav>
  </div>
</html>

 

결과화면

 

다음장에는 게시판 검색, 게시판 페이징을 개발하겠습니다.

 

다음글보기

https://aamoos.tistory.com/676

 

[Spring Jpa] 7. 게시판 만들기 - 게시판 검색, 페이징 검색

소스 BoardRepositoryImpl.java 수정 package jpa.board.repositoryImpl; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import jpa.board.dto.BoardDt..

aamoos.tistory.com

 

복사했습니다!