Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

어리바리 신입 개발자의 얼렁뚱땅 개발 기록 ✨

[ Java / SpringBoot / MVC / RESTful] RESTful한 게시판 만들기9 / 게시글 목록에 페이징 추가하기 (DTO와 본문

REST API

[ Java / SpringBoot / MVC / RESTful] RESTful한 게시판 만들기9 / 게시글 목록에 페이징 추가하기 (DTO와

낫쏘링 2023. 9. 12. 17:24
728x90
[ DTO VS VO]
- DTO (Data Transfer Object) / 가변 객체
  데이터를 계층간 교환하기 위해 사용한다. 즉, 로직을 가지지 않는 순수하게 데이터를 담고 전달하는 객체이다.
  때문에 getter, setter, toString 정도의 메서드만 갖고 있다.
- VO (Value Object) / 불변 객체
  Read-Only의 특징을 지닌다. 즉, 데이터를 전달만 할 뿐 setter가 존재하지 않는다. 
  불변 객체라는 게 무슨 뜻이지...? 값이 바뀌지 않고 오직 Read만 한다고..?
  값이 바뀌지 않는다면 그냥 상수를 말하는 거 아닌가?
  상수를 쓸거면 굳이 VO를 쓸 이유가 있나? 라는 생각을했는데
  VO는 setter가 아니라 반듯이 생성자를 생성해서 사용한다는 점에서 불변 객체라는거였다.
  이게 무슨 뜻이냐면 아래 처럼 처음 사용할 때 생성자를 통해서 값을 넣어주면
  그 이후로 setter를 사용할 수 없기 때문에 처음 생성한 그 상태에서 불변한 객체라는 뜻이다.

 

PagingVo paging = new PagingVo(1,3);​
  만약 새로운 값이 필요하면 또 아래처럼 새롭게 생성자를 생성해서 새로운 객체를 생성해야 한다.
PagingVo paiging2 = new PagingVo(4,9);​

지금까지는 DTO를 사용해서 데이터를 주고 받았다.
페이징을 구현하기 위해 VO를 사용해 볼 생각이다.
[ 1. 게시글 개수 조회하기 ]
<select id="selectCount" resultType="int">
    /* 게시글 개수 조회 */
    <![CDATA[
        SELECT
            COUNT(*)
        FROM
            t_board
        WHERE
            deleted_yn = 'N'
    ]]>        
</select>​
/** 게시글 개수 조회 mapper */
int selectCount() throws Exception;
[ 2. PagingVo 생성 ]
@Getter
@ToString
public class PagingVo {
	private int rowsCount;
	private int currentPage;
	private int totalPage;
	private int startPage;
	private int endPage;
	private int startIndex;
	private int rowPerPage = 10;
	public static final int PAGE_NUM = 10;
	
	/** 생성자 메서드*/
	public PagingVo (int currentPage, int rowsCount) throws Exception{
		this.currentPage = currentPage;
		
		// 몇 번째 행 부터 조회할 것인지 계산
		startIndex = (currentPage-1)*rowPerPage;
		
		// 전체 게시글 개수를 한 페이지에 보여질 행의 개수로 나누어서 페이징의 가장 마지막 숫자(총 페이징 수)를 계산한다.
		// double로 계산 후에 소숫점을 올림 처리하기 위해 Math.ceil 사용 후 int로 형변환
		totalPage = (int) Math.ceil((double)rowsCount / rowPerPage);

		// 페이징의 가장 처음 번호(처음에는 당연히 1 -> currentPage가 바뀔 때마다 바뀔 예정)
		startPage = 1;

		// 화면에 보이는 마지막 페이징 숫자(currentPage가 바뀔 때마다 바뀔 예정)
		// rowPerPage는 한 페이지에 보일 행의 개수 / endPage는 한 페이지에 보일 페이징의 마지막 번호
		endPage = (totalPage < PAGE_NUM)? totalPage : PAGE_NUM;
		
		// currentPage가 바뀔 때마다 startPge와 endPage가 바뀐다.
		// 페이징이 처음에 1~10이기 때문에 currentPage가 6이되는 순간 startPager가 2로, endPage가 11로 바뀐다.
		// totalPage에도 조건을 거는 이유는 만약 게시글이 적어서 totalPage가 10보다 적을 경우
		// 페이징이 시작번호와 끝 번호가 바뀔 이유가 없기 때문
		if(currentPage >= 6 && totalPage > 10) {
	    	startPage = currentPage - 5;
	    	endPage = currentPage + 4;
	        // 현재 페이지에 보이는 페이징 번호가 마지막 페이징 숫자보다 같거나 클 경우
	        // 더 이상 시작 번호와 끝 번호가 바뀌지 않도록 한다.
	        if(endPage >= totalPage) {
	        	startPage = totalPage - 9;
	        	endPage = totalPage;
	        }
	    }
	}
}​
[ 3. BoardPagingDto 생성 ]
@Getter
@Setter
@ToString
@AllArgsConstructor
public class BoardPagingDto {
	private List<BoardDto> boardList;
    private PagingVo paging;
}​
[ 4. 게시글 조회 쿼리 수정 ]
<select id="selectBoardList" resultType="BoardDto">
    /* 기존 게시글 내역 조회 */
    <![CDATA[
        SELECT
            board_idx,
            title,
            hit_cnt,
            created_datetime
        FROM
            t_board
        WHERE
            deleted_yn = 'N'
        ORDER BY board_idx DESC		
    ]]>		
</select>

<select id="selectBoardList" parameterType="PagingVo" resultType="BoardDto">
    /* 수정 후 게시글 내역 조회 */
    <![CDATA[
        SELECT
            board_idx,
            title,
            hit_cnt,
            created_datetime
        FROM
            t_board
        WHERE
            deleted_yn = 'N'
        ORDER BY board_idx DESC		
    ]]>		
    <if test="startIndex != null and startIndex > -1 ">
        LIMIT #{startIndex},#{rowPerPage};
    </if>
</select>
/** 게시글 내역 조회 mapper 매개변수 수정 */
List<BoardDto> selectBoardList(PagingVo paging) throws Exception;
[ 5. BoardServie 추가 및 수정 ]
/** 페이징 */
public PagingVo selectPaging(int currentPage) throws Exception{
     int rowsCount = boardMapper.selectCount();	
     PagingVo paging = new PagingVo(currentPage, rowsCount);

     return paging;		
}

/** 게시글 내역 조회 */
public BoardPagingDto selectBoardList(int currentPage) throws Exception{
    PagingVo paging = selectPaging(currentPage);
    List<BoardDto> boardList = boardMapper.selectBoardList(paging);		
    BoardPagingDto boardPaging = new BoardPagingDto(boardList, paging);		

    return boardPaging;
}​
[ 6. BoardController 수정 ]
/** 기존 게시판 목록 조회 화면 */
@GetMapping("/board")
public String openBoardList(Model model) throws Exception{
    BoardDto boardList = boardService.selectBoardList();

    model.addAttribute("title","게시판 목록");
    model.addAttribute("boardList", boardList);

    return "/board/restBoardList";		
}

/** 수정 후 게시판 목록 조회 화면 */
@GetMapping("/board")
public String openBoardList(@RequestParam(value="currentPage", required = false ,defaultValue = "1") int currentPage, 
                             Model model) throws Exception{
    BoardPagingDto boardPaging = boardService.selectBoardList(currentPage);

    model.addAttribute("title","게시판 목록");
    model.addAttribute("boardPaging", boardPaging);

    return "/board/restBoardList";		
}​
[ 7. html 수정 ]
<!-- 페이징 구현 태그 추가 -->
<!-- 가장 첫 페이지거나 마지막 페이지일 경우 혹은 현재 페이지일 경우 label 태그를 그린다. -->
<!-- 그 외의 경우 a 태그를 그린다. -->
<tfoot>
     <tr>
        <td colspan="4" class="text-center" th:with="paging=${boardPaging.paging}">						
            <a class="btn" th:if="${paging.currentPage>1}" th:href="@{/board}">처음으로</a>
            <label th:unless="${paging.currentPage>1}">처음으로</label>
            <a class="btn" th:if="${paging.currentPage>1}" th:href="@{/board(currentPage=${paging.currentPage-1})}">이전</a>
            <label th:unless="${paging.currentPage>1}">이전</label>
                <th:block th:each="num : ${#numbers.sequence(paging.startPage,paging.endPage)}" >
                    <a class="btn " th:if="${paging.currentPage != num}" th:href="@{/board(currentPage=${num})}" th:text="${num}"></a>
                    <label class="current-page-btn" th:if="${paging.currentPage == num}" th:text="${num}"></label>
                </th:block>
            <a class="btn" th:if="${paging.currentPage<paging.totalPage}" th:href="@{/board(currentPage=${paging.currentPage+1})}">다음</a>
            <label th:unless="${paging.currentPage<paging.totalPage}">다음</label>
            <a class="btn" th:if="${paging.currentPage<paging.totalPage}" th:href=@{/board(currentPage=${paging.totalPage})}>마지막으로</a>
            <label th:unless="${paging.currentPage<paging.totalPage}">마지막으로</label>
        </td>
    </tr>
</tfoot>
<!--tbody에서 ${#lists.size(boardList)}를 ${#lists.size(boardPaging.boardList)}로 수정 -->
<!--서버에서 받아오는 데이터의 형태가 바뀌었기 때문에 boardList 앞에 boardPaging. 추가해줘야한다. -->
<tbody>
    <!-- th:if - 타임리프 문법을 사용 할 경우에 쓰는 if 문 -->
    <!-- if문 내의 조건이 일치해야만 해당 tr이 생성된다. -->
    <!-- th:each - 타임리프 문법을 사용할 경우에 쓰는 반복문 ( ${list}에 들어있는 값을 모두 순회하면서 tr 태그를 생성한다. )-->
    <tr th:if="${#lists.size(boardPaging.boardList)} > 0" th:each="list : ${boardPaging.boardList}">
        <td th:text="${list.boardIdx}"></td>
        <!-- th:href -  타임리프 문법을 사용할 경우에 쓰는 href 속성 -->
        <!-- href="/board/" th:attrappend="href=${list.boardIdx}" 로도 작성 가능하다. -->
        <td class="title"><a th:href="'/board/' + ${list.boardIdx}"  th:text="${list.title}"></a></td>
        <td th:text="${list.hitCnt}"></td>
        <td th:text="${list.createdDatetime}"></td>
    </tr> 
    <!-- th:unless - 타임리프 문법을 사용할 경우에 쓴는 if 문 / 단, if와 반대로 조건이 일치하지 않을 경우 tr 생성 -->
    <tr th:unless="${#lists.size(boardPaging.boardList)} > 0">
        <td colspan="4">조회된 결과가 없습니다.</td>
    </tr>
</tbody>

 

페이징이 구현 됐다. 데이터를 많이 넣지 않아서 페이징이 10까지 나오지 않고 4까지만 나온다.

currentPage가 마지막인 상태에서 다음,마지막으로 버튼이 label태그로 변한 상태

 

다른 페이지를 클릭할 경우 처음으로와 이전버튼이 a태그로 바뀌고 활성화된다.

 

7을 누르면 페이징 범위가 2에서 11로 바뀐다.

728x90