Skip to main content

03B. ํŽ˜์ด์ง•

About 3 minJavaSpringAWScrashcoursejavajdkjdk8streamspringspringframeworkspringbootawsaws-ec2

03B. ํŽ˜์ด์ง• ๊ด€๋ จ


3-02. ํŽ˜์ด์ง•

์ ํ”„ ํˆฌ ์Šคํ”„๋ง๋ถ€ํŠธ - WikiDocs

pahkey/sbb3 - 3-02open in new window

SBB์˜ ์งˆ๋ฌธ ๋ชฉ๋ก์€ ํ˜„์žฌ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๊ฐ€ ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฒŒ์‹œ๋ฌผ 300๊ฐœ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ํ•œ ํŽ˜์ด์ง€์— 300๊ฐœ์˜ ๊ฒŒ์‹œ๋ฌผ์ด ๋ชจ๋‘ ์กฐํšŒ๋œ๋‹ค. ์ด๋ฒˆ ์žฅ์—์„œ๋Š” ํŽ˜์ด์ง•(Paging)์„ ์ ์šฉํ•˜์—ฌ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด ๋ณด์ž.


๋Œ€๋Ÿ‰ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ๋งŒ๋“ค๊ธฐ

ํŽ˜์ด์ง•์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์ „์— ํŽ˜์ด์ง•์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์„ ์ •๋„๋กœ ์ถฉ๋ถ„ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜์ž. ๋Œ€๋Ÿ‰์˜ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ ์Šคํ”„๋ง๋ถ€ํŠธ์˜ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๋‹ค์Œ์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ•˜์ž.

ํŒŒ์ผ๋ช…: /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);
        }
    }
}

์ด 300๊ฐœ์˜ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

๋งŒ์•ฝ ๋กœ์ปฌ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰ ์ค‘์ด๋ผ๋ฉด ๋กœ์ปฌ ์„œ๋ฒ„๋ฅผ ์ค‘์ง€ํ•˜๊ณ  [Run -> Run As -> Junit Test]๋กœ testJpa ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜์ž. ๊ทธ๋ฆฌ๊ณ  ๋‹ค์‹œ ๋กœ์ปฌ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•œ ํ›„์— ๋ธŒ๋ผ์šฐ์ €์—์„œ ์งˆ๋ฌธ ๋ชฉ๋ก์„ ํ™•์ธํ•ด ๋ณด์ž.

02_1
02_1

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋กœ ๋“ฑ๋กํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณด์ผ ๊ฒƒ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  300๊ฐœ ์ด์ƒ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•œ ํŽ˜์ด์ง€ ๋ณด์—ฌ์ง€๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ด์œ ๋กœ ํŽ˜์ด์ง•์€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๋“ฑ๋กํ•œ ๊ฒŒ์‹œ๋ฌผ๋„ ์ตœ๊ทผ ์ˆœ์œผ๋กœ ๋ณด์—ฌ์•ผ ํ•˜๋Š”๋ฐ ๋“ฑ๋กํ•œ ์ˆœ์„œ๋กœ ๋ณด์ด๋Š” ๋ฌธ์ œ๋„ ์žˆ๋‹ค. ์ด ๋ฌธ์ œ๋„ ํ•จ๊ป˜ ํ•ด๊ฒฐํ•ด ๋ณด์ž.


ํŽ˜์ด์ง• ๊ตฌํ˜„ํ•˜๊ธฐ

ํŽ˜์ด์ง•์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€๋กœ ์„ค์น˜ํ•ด์•ผ ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์—†๋‹ค. JPA ํ™˜๊ฒฝ ๊ตฌ์ถ•์‹œ ์„ค์น˜ํ–ˆ๋˜ JPA ๊ด€๋ จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ด๋ฏธ ํŽ˜์ด์ง•์„ ์œ„ํ•œ ํŒจํ‚ค์ง€๋“ค์ด ๋“ค์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋‹ค์Œ์˜ ํด๋ž˜์Šค๋“ค์„ ์ด์šฉํ•˜๋ฉด ํŽ˜์ด์ง•์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • org.springframework.data.domain.Page
  • org.springframework.data.domain.PageRequest
  • org.springframework.data.domain.Pageable

์œ„์— ์†Œ๊ฐœํ•œ ํด๋ž˜์Šค๋ฅผ ์ ์šฉํ•ด ํŽ˜์ด์ง•์„ ๊ตฌํ˜„ํ•ด ๋ณด์ž. ๋จผ์ € Question ๋ฆฌํฌ์ง€ํ„ฐ๋ฆฌ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ findAll ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/question/QuestionRepository.java

package com.mysite.sbb.question;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface QuestionRepository extends JpaRepository<Question, Integer> {
    Question findBySubject(String subject);
    Question findBySubjectAndContent(String subject, String content);
    List<Question> findBySubjectLike(String subject);
    Page<Question> findAll(Pageable pageable);
}




ย 
ย 






ย 

Pageable ๊ฐ์ฒด๋ฅผ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›์•„ Page<Question> ํƒ€์ž… ๊ฐ์ฒด๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” findAll ๋ฉ”์„œ๋“œ๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Question ์„œ๋น„์Šค๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/question/QuestionService.java

// (... ์ƒ๋žต ...)
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
// (... ์ƒ๋žต ...)
public class QuestionService {

    // (... ์ƒ๋žต ...)

    public Page<Question> getList(int page) {
        Pageable pageable = PageRequest.of(page, 10);
        return this.questionRepository.findAll(pageable);
    }

    // (... ์ƒ๋žต ...)
}

ย 
ย 
ย 





ย 
ย 
ย 
ย 



์งˆ๋ฌธ ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” getList ๋ฉ”์„œ๋“œ๋ฅผ ์œ„์™€ ๊ฐ™์ด ๋ณ€๊ฒฝํ–ˆ๋‹ค. getList ๋ฉ”์„œ๋“œ๋Š” ์ •์ˆ˜ ํƒ€์ž…์˜ ํŽ˜์ด์ง€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ํ•ด๋‹น ํŽ˜์ด์ง€์˜ ์งˆ๋ฌธ ๋ชฉ๋ก์„ ๋ฆฌํ„ดํ•˜๋Š” ๋ฉ”์„œ๋“œ๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค. Pageable ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ• ๋•Œ ์‚ฌ์šฉํ•œ PageRequest.of(page, 10)์—์„œ page๋Š” ์กฐํšŒํ•  ํŽ˜์ด์ง€์˜ ๋ฒˆํ˜ธ์ด๊ณ  10์€ ํ•œ ํŽ˜์ด์ง€์— ๋ณด์—ฌ์ค„ ๊ฒŒ์‹œ๋ฌผ์˜ ๊ฐฏ์ˆ˜๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์ „์ฒด๋ฅผ ์กฐํšŒํ•˜์ง€ ์•Š๊ณ  ํ•ด๋‹น ํŽ˜์ด์ง€์˜ ๋ฐ์ดํ„ฐ๋งŒ ์กฐํšŒํ•˜๋„๋ก ์ฟผ๋ฆฌ๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค.

Question ์„œ๋น„์Šค์˜ getList ๋ฉ”์„œ๋“œ์˜ ์ž…์ถœ๋ ฅ ๊ตฌ์กฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์œผ๋ฏ€๋กœ Question ์ปจํŠธ๋กค๋Ÿฌ๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค.

ํŒŒ์ผ๋ช…: sbb/src/main/java/com/mysite/sbb/question/QuestionController.java

package com.mysite.sbb.question;

// (... ์ƒ๋žต ...)
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.data.domain.Page;
// (... ์ƒ๋žต ...)
public class QuestionController {

    // (... ์ƒ๋žต ...)

    @GetMapping("/list")
    public String list(Model model, @RequestParam(value="page", defaultValue="0") int page) {
        Page<Question> paging = this.questionService.getList(page);
        model.addAttribute("paging", paging);
        return "question_list";
    }

    // (... ์ƒ๋žต ...)
}



ย 
ย 






ย 
ย 
ย 





http://localhost:8080/question/list?page=0 ์ฒ˜๋Ÿผ GET ๋ฐฉ์‹์œผ๋กœ ์š”์ฒญ๋œ URL์—์„œ page๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด @RequestParam(value="page", defaultValue="0") int page ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ list ๋ฉ”์„œ๋“œ์— ์ถ”๊ฐ€๋˜์—ˆ๋‹ค. URL์— ํŽ˜์ด์ง€ ํŒŒ๋ผ๋ฏธํ„ฐ page๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋””ํดํŠธ ๊ฐ’์œผ๋กœ 0์ด ๋˜๋„๋ก ์„ค์ •ํ–ˆ๋‹ค.

์Šคํ”„๋ง๋ถ€ํŠธ์˜ ํŽ˜์ด์ง•์€ ์ฒซํŽ˜์ด์ง€ ๋ฒˆํ˜ธ๊ฐ€ 1์ด ์•„๋‹Œ 0์ด๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ…œํ”Œ๋ฆฟ์— Page<Question> ๊ฐ์ฒด์ธ paging์„ ์ „๋‹ฌํ–ˆ๋‹ค. Page ๊ฐ์ฒด์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์†์„ฑ์ด ์žˆ๋‹ค. ๋‹ค์Œ์˜ ์†์„ฑ๋“ค์€ ํ…œํ”Œ๋ฆฟ์—์„œ ํŽ˜์ด์ง•์„ ์ฒ˜๋ฆฌํ•  ๋•Œ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

ํ•ญ๋ชฉ์„ค๋ช…
paging.isEmptyํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€ (๊ฒŒ์‹œ๋ฌผ์ด ์žˆ์œผ๋ฉด false, ์—†์œผ๋ฉด true)
paging.totalElements์ „์ฒด ๊ฒŒ์‹œ๋ฌผ ๊ฐœ์ˆ˜
paging.totalPages์ „์ฒด ํŽ˜์ด์ง€ ๊ฐœ์ˆ˜
paging.sizeํŽ˜์ด์ง€๋‹น ๋ณด์—ฌ์ค„ ๊ฒŒ์‹œ๋ฌผ ๊ฐœ์ˆ˜
paging.numberํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ
paging.hasPrevious์ด์ „ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€
paging.hasNext๋‹ค์Œ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์กด์— ์ „๋‹ฌํ–ˆ๋˜ ์ด๋ฆ„์ธ "questionList" ๋Œ€์‹  "paging" ์ด๋ฆ„์œผ๋กœ ํ…œํ”Œ๋ฆฟ์— ์ „๋‹ฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ…œํ”Œ๋ฆฟ๋„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค.

ํŒŒ์ผ๋ช…: /sbb/src/main/resources/templates/question_list.html

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
    <table class="table">
        <!-- (... ์ƒ๋žต ...) -->
        <tbody>
            <tr th:each="question, loop : ${paging}">
                <!-- (... ์ƒ๋žต ...) -->
            </tr>
        </tbody>
    </table>
    <a th:href="@{/question/create}" class="btn btn-primary">์งˆ๋ฌธ ๋“ฑ๋กํ•˜๊ธฐ</a>
</div>
</html>





ย 







์ด๋ ‡๊ฒŒ ์ˆ˜์ •ํ•˜๊ณ  ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:8080/question/list?page=0 URL์„ ์š”์ฒญํ•ด ๋ณด์ž. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฒซํŽ˜์ด์ง€์— ํ•ด๋‹นํ•˜๋Š” ๊ฒŒ์‹œ๋ฌผ 10๊ฐœ๋งŒ ์กฐํšŒ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

02_2
02_2

๋‹ค์‹œ http://localhost:8080/question/list?page=1 URL์„ ์š”์ฒญํ•˜๋ฉด ๋‘๋ฒˆ์งธ ํŽ˜์ด์ง€์— ํ•ด๋‹นํ•˜๋Š” ๊ฒŒ์‹œ๋ฌผ์ด ์กฐํšŒ๋œ๋‹ค.

02_3
02_3

ํ…œํ”Œ๋ฆฟ์— ํŽ˜์ด์ง€ ์ด๋™ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

์งˆ๋ฌธ ๋ชฉ๋ก์—์„œ ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•˜๋ ค๋ฉด ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ["์ด์ „"], ["๋‹ค์Œ"] ๊ณผ ๊ฐ™์€ ๋งํฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ์ด๋ฒˆ์—๋Š” ์งˆ๋ฌธ ๋ชฉ๋ก์— ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๋งํฌ๋“ค์„ ์ถ”๊ฐ€ํ•ด ๋ณด์ž.

.question_list.html ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์˜ </table> ํƒœ๊ทธ ๋ฐ”๋กœ ๋ฐ‘์— ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/resources/templates/question_list.html

<html layout:decorate="~{layout}">
<div layout:fragment="content" class="container my-3">
    <table class="table">
        <!-- (... ์ƒ๋žต ...) -->
    </table>
    <!-- ํŽ˜์ด์ง•์ฒ˜๋ฆฌ ์‹œ์ž‘ -->
    <div th:if="${!paging.isEmpty()}">
        <ul class="pagination justify-content-center">
            <li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
                <a class="page-link"
                    th:href="@{|?page=${paging.number-1}|}">
                    <span>์ด์ „</span>
                </a>
            </li>
            <li th:each="page: ${#numbers.sequence(0, paging.totalPages-1)}"
                th:classappend="${page == paging.number} ? 'active'" 
                class="page-item">
                <a th:text="${page}" class="page-link" th:href="@{|?page=${page}|}"></a>
            </li>
            <li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
                <a class="page-link" th:href="@{|?page=${paging.number+1}|}">
                    <span>๋‹ค์Œ</span>
                </a>
            </li>
        </ul>
    </div>
    <!-- ํŽ˜์ด์ง•์ฒ˜๋ฆฌ ๋ -->
    <a th:href="@{/question/create}" class="btn btn-primary">์งˆ๋ฌธ ๋“ฑ๋กํ•˜๊ธฐ</a>
</div>
</html>





ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 



์ƒ๋‹นํžˆ ๋งŽ์€ ์–‘์˜ HTML์ฝ”๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์ง€๋งŒ ์–ด๋ ต์ง€ ์•Š์œผ๋‹ˆ ์ฐฌ์ฐฌํžˆ ์‚ดํŽด๋ณด์ž.

์ด์ „ ํŽ˜์ด์ง€๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ์—๋Š” "์ด์ „" ๋งํฌ๊ฐ€ ๋น„ํ™œ์„ฑํ™”(disabled)๋˜๋„๋ก ํ•˜์˜€๋‹ค. (๋‹ค์ŒํŽ˜์ด์ง€์˜ ๊ฒฝ์šฐ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์ ์šฉํ–ˆ๋‹ค.) ๊ทธ๋ฆฌ๊ณ  ํŽ˜์ด์ง€ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฃจํ”„ ๋Œ๋ฉด์„œ ํ•ด๋‹น ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๋งํฌ๋ฅผ ์ƒ์„ฑํ•˜์˜€๋‹ค. ์ด๋•Œ ๋ฃจํ”„ ๋„์ค‘์˜ ํŽ˜์ด์ง€๊ฐ€ ํ˜„์žฌ ํŽ˜์ด์ง€์™€ ๊ฐ™์„ ๊ฒฝ์šฐ์—๋Š” activeํด๋ž˜์Šค๋ฅผ ์ ์šฉํ•˜์—ฌ ๊ฐ•์กฐํ‘œ์‹œ(์„ ํƒํ‘œ์‹œ)๋„ ํ•ด ์ฃผ์—ˆ๋‹ค.

ํƒ€์ž„๋ฆฌํ”„์˜ th:classappend="์กฐ๊ฑด์‹ ? ํด๋ž˜์Šค๊ฐ’" ์†์„ฑ์€ ์กฐ๊ฑด์‹์ด ์ฐธ์ธ ๊ฒฝ์šฐ ํด๋ž˜์Šค๊ฐ’์„ class ์†์„ฑ์— ์ถ”๊ฐ€ํ•œ๋‹ค.

์œ„ ํ…œํ”Œ๋ฆฟ์— ์‚ฌ์šฉ๋œ ์ฃผ์š” ํŽ˜์ด์ง• ๊ธฐ๋Šฅ์„ ํ‘œ๋กœ ์ •๋ฆฌํ•ด ๋ณด์•˜๋‹ค.

ํŽ˜์ด์ง• ๊ธฐ๋Šฅ์ฝ”๋“œ
์ด์ „ ํŽ˜์ด์ง€๊ฐ€ ์—†์œผ๋ฉด ๋น„ํ™œ์„ฑํ™”th:classappend="${!paging.hasPrevious} ? 'disabled'"
๋‹ค์Œ ํŽ˜์ด์ง€๊ฐ€ ์—†์œผ๋ฉด ๋น„ํ™œ์„ฑํ™”th:classappend="${!paging.hasNext} ? 'disabled'"
์ด์ „ ํŽ˜์ด์ง€ ๋งํฌ`th:href="@{
๋‹ค์Œ ํŽ˜์ด์ง€ ๋งํฌ`th:href="@{
ํŽ˜์ด์ง€ ๋ฆฌ์ŠคํŠธ ๋ฃจํ”„th:each="page: ${#numbers.sequence(0, paging.totalPages-1)}"
ํ˜„์žฌ ํŽ˜์ด์ง€์™€ ๊ฐ™์œผ๋ฉด active ์ ์šฉth:classappend="${page == paging.number} ? 'active'"

#numbers.sequence(์‹œ์ž‘, ๋)์€ ์‹œ์ž‘ ๋ฒˆํ˜ธ๋ถ€ํ„ฐ ๋ ๋ฒˆํ˜ธ๊นŒ์ง€์˜ ๋ฃจํ”„๋ฅผ ๋งŒ๋“ค์–ด ๋‚ด๋Š” ํƒ€์ž„๋ฆฌํ”„์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํŽ˜์ด์ง€ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด๊ธฐ ์ข‹๊ฒŒ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์˜ pagination ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•˜์˜€๋‹ค. ํ…œํ”Œ๋ฆฟ์— ์‚ฌ์šฉํ•œ pagination, page-item, page-link ๋“ฑ์ด ๋ถ€ํŠธ์ŠคํŠธ๋žฉ pagination ์ปดํฌ๋„ŒํŠธ์˜ ํด๋ž˜์Šค์ด๋‹ค.

๐Ÿ“š๋ถ€ํŠธ์ŠคํŠธ๋žฉ paginationopen in new window


ํŽ˜์ด์ง€ ๋ฆฌ์ŠคํŠธ

์—ฌ๊ธฐ๊นŒ์ง€ ์ˆ˜์ •ํ•˜๊ณ  ์งˆ๋ฌธ ๋ชฉ๋ก์„ ์กฐํšŒํ•ด ๋ณด์ž.

02_4
02_4

ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋Š” ์ž˜ ๋˜์—ˆ์ง€๋งŒ ํ•œ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ๋ฐœ๊ฒฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฌธ์ œ๋Š” ์œ„์—์„œ ๋ณด๋“ฏ์ด ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ํŽ˜์ด์ง€๊ฐ€ ๋ชจ๋‘ ํ‘œ์‹œ๋œ๋‹ค๋Š” ์ ์ด๋‹ค.

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์งˆ๋ฌธ ๋ชฉ๋ก ํ…œํ”Œ๋ฆฟ์„ ์ˆ˜์ •ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/resources/templates/question_list.html

 <!-- (... ์ƒ๋žต ...) -->
    <!-- ํŽ˜์ด์ง•์ฒ˜๋ฆฌ ์‹œ์ž‘ -->
    <div th:if="${!paging.isEmpty()}">
        <ul class="pagination justify-content-center">
            <li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
                <a class="page-link"
                    th:href="@{|?page=${paging.number-1}|}">
                    <span>์ด์ „</span>
                </a>
            </li>
            <li th:each="page: ${#numbers.sequence(0, paging.totalPages-1)}" 
                th:if="${page >= paging.number-5 and page <= paging.number+5}"
                th:classappend="${page == paging.number} ? 'active'" 
                class="page-item">
                <a th:text="${page}" class="page-link" th:href="@{|?page=${page}|}"></a>
            </li>
            <li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
                <a class="page-link" th:href="@{|?page=${paging.number+1}|}">
                    <span>๋‹ค์Œ</span>
                </a>
            </li>
        </ul>
    </div>
    <!-- ํŽ˜์ด์ง•์ฒ˜๋ฆฌ ๋ -->
    <a th:href="@{/question/create}" class="btn btn-primary">์งˆ๋ฌธ ๋“ฑ๋กํ•˜๊ธฐ</a>
</div>
</html>














ย 












๋‹ค์Œ์˜ ์ฝ”๋“œ๋ฅผ ์‚ฝ์ž…ํ•˜์—ฌ ํŽ˜์ด์ง€ ํ‘œ์‹œ ์ œํ•œ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

th:if="${page >= paging.number-5 and page <= paging.number+5}"

์ด ์ฝ”๋“œ๋Š” ํŽ˜์ด์ง€ ๋ฆฌ์ŠคํŠธ๊ฐ€ ํ˜„์žฌ ํŽ˜์ด์ง€ ๊ธฐ์ค€์œผ๋กœ ์ขŒ์šฐ 5๊ฐœ์”ฉ ๋ณด์ด๋„๋ก ๋งŒ๋“ ๋‹ค. ๋ฃจํ”„๋‚ด์— ํ‘œ์‹œ๋˜๋Š” ํŽ˜์ด์ง€๊ฐ€ ํ˜„์žฌ ํŽ˜์ด์ง€๋ฅผ ์˜๋ฏธํ•˜๋Š” paging.number ๋ณด๋‹ค 5๋งŒํผ ์ž‘๊ฑฐ๋‚˜ ํฐ ๊ฒฝ์šฐ์—๋งŒ ํ‘œ์‹œ๋˜๋„๋ก ํ•œ ๊ฒƒ์ด๋‹ค.

๋งŒ์•ฝ ํ˜„์žฌ ํŽ˜์ด์ง€๊ฐ€ 15๋ผ๋ฉด ๋‹ค์Œ์ฒ˜๋Ÿผ ํŽ˜์ด์ง€ ๋ฆฌ์ŠคํŠธ๊ฐ€ ํ‘œ์‹œ๋  ๊ฒƒ์ด๋‹ค.

02_5
02_5

์ž‘์„ฑ์ผ์‹œ ์—ญ์ˆœ์œผ๋กœ ์กฐํšŒํ•˜๊ธฐ

ํ˜„์žฌ ์งˆ๋ฌธ ๋ชฉ๋ก์€ ๋“ฑ๋กํ•œ ์ˆœ์„œ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ํ‘œ์‹œ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๊ฒŒ์‹œํŒ์€ ๊ฐ€์žฅ ์ตœ๊ทผ์— ์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๋ฌผ์ด ๊ฐ€์žฅ ๋จผ์ € ๋ณด์ด๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋‹ค. ์ด๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด Question ์„œ๋น„์Šค๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์ž.

ํŒŒ์ผ๋ช…: /sbb/src/main/java/com/mysite/sbb/question/QuestionService.java

// (... ์ƒ๋žต ...)
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.Sort;
// (... ์ƒ๋žต ...)
public class QuestionService {

   // (... ์ƒ๋žต ...)

    public Page<Question> getList(int page) {
        List<Sort.Order> sorts = new ArrayList<>();
        sorts.add(Sort.Order.desc("createDate"));
        Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
        return this.questionRepository.findAll(pageable);
    }

    // (... ์ƒ๋žต ...)
}

ย 
ย 
ย 






ย 
ย 






๊ฒŒ์‹œ๋ฌผ์„ ์—ญ์ˆœ์œผ๋กœ ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์œ„์™€ ๊ฐ™์ด PageRequest.of ๋ฉ”์„œ๋“œ์˜ ์„ธ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ Sort ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค. Sort.Order ๊ฐ์ฒด๋กœ ๊ตฌ์„ฑ๋œ ๋ฆฌ์ŠคํŠธ์— Sort.Order ๊ฐ์ฒด๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  Sort.by(์†ŒํŠธ๋ฆฌ์ŠคํŠธ)๋กœ ์†ŒํŠธ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค. ์ž‘์„ฑ์ผ์‹œ(createDate)๋ฅผ ์—ญ์ˆœ(Desc)์œผ๋กœ ์กฐํšŒํ•˜๋ ค๋ฉด Sort.Order.desc("createDate") ๊ฐ™์ด ์ž‘์„ฑํ•œ๋‹ค.

๋งŒ์•ฝ ์ž‘์„ฑ์ผ์‹œ ์™ธ์— ์ถ”๊ฐ€๋กœ ์ •๋ ฌ์กฐ๊ฑด์ด ํ•„์š”ํ•  ๊ฒฝ์šฐ์—๋Š” sorts ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

์ด๋ ‡๊ฒŒ ์ˆ˜์ •ํ•˜๊ณ  ์ฒซ๋ฒˆ์งธ ํŽ˜์ด์ง€๋ฅผ ์กฐํšŒํ•˜๋ฉด ์ด์ œ ๊ฐ€์žฅ ์ตœ๊ทผ์— ๋“ฑ๋ก๋œ ์ˆœ์œผ๋กœ ๊ฒŒ์‹œ๋ฌผ์ด ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•œ ์ˆ˜ ์žˆ๋‹ค.

02_6
02_6

์ถ•ํ•˜ํ•œ๋‹ค! ํŽ˜์ด์ง• ๊ธฐ๋Šฅ์ด ์™„์„ฑ๋˜์—ˆ๋‹ค. ํŽ˜์ด์ง•์€ ์‚ฌ์‹ค ๊ตฌํ˜„ํ•˜๊ธฐ ๊นŒ๋‹ค๋กœ์šด ๊ธฐ์ˆ ์ด๋‹ค. ํŽ˜์ด์ง• ํด๋ž˜์Šค์˜ ๋„์›€์ด ์—†์—ˆ๋‹ค๋ฉด ์•„๋งˆ ์ด๋ ‡๊ฒŒ ์‰ฝ๊ฒŒ ํ•ด๋‚ด๊ธฐ๋Š” ํž˜๋“ค์—ˆ์„ ๊ฒƒ์ด๋‹ค.

์ง€๊ธˆ๊นŒ์ง€ ๋งŒ๋“  ํŽ˜์ด์ง• ๊ธฐ๋Šฅ์— '์ฒ˜์Œ'๊ณผ '๋งˆ์ง€๋ง‰' ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  'โ€ฆ' ์ƒ๋žต ๊ธฐํ˜ธ๊นŒ์ง€ ์ถ”๊ฐ€ํ•˜๋ฉด ๋” ์™„์„ฑ๋„ ๋†’์€ ํŽ˜์ด์ง• ๊ธฐ๋Šฅ์ด ๋  ๊ฒƒ์ด๋‹ค.


์ด์ฐฌํฌ (MarkiiimarK)
Never Stop Learning.