Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
85
Help us understand the problem. What is going on with this article?
@KevinFQ

Spring Boot + Thymeleafでページング機能を実装する

More than 3 years have passed since last update.

Spring Boot + Thymeleafで画面のページング機能を簡単に実装できます。
完成後の画面イメージ
スクリーンショット 2017-01-21 23.25.18.png

検証環境

  • Spring Boot 1.4.3
  • PostgreSQL 9.3.15

実装

まずは、Domainレイヤです。

@Entity
@Table(name="word_info")
public class Word implements Serializable {

    private static final long serialVersionUID = -870708489937857961L;

    @Id
    @GeneratedValue(strategy=GenerationType.TABLE, generator="seqTable")
    @TableGenerator(name="seqTable", table="seq_table", pkColumnName="seq_name", pkColumnValue="word_seq", valueColumnName="seq_value")
    @Column(name="id")
    private Long id;

    @Column(name="word")
    private String word;

    @Column(name="meaning")
    private String meaning;

    @Column(name="example")
    private String example;

    //get・set省略
    ...
}

一覧を取得するために、JPAのリポジトリクラスを定義する。Listの代わりにPageクラスを使用します。

@Repository
public interface WordRepository extends CrudRepository<Word, Long>{

    public Page<Word> findAll(Pageable pageable);

}

次は、Serviceレイヤです。

@Service
public class WordService {

    @Autowired
    private WordRepository wordRepo;

    public Page<Word> getAllWord(Pageable pageable) {

        return wordRepo.findAll(pageable);
    }

}

Presentationレイヤです。

@Controller
public class MainController {

    @Autowired
    private WordService wordService;

    @RequestMapping(value="/word/wordList", method=RequestMethod.GET)
    public String getWordList(Model model, Pageable pageable) {
        Page<Word> wordsPage = wordService.getAllWord(pageable);
        model.addAttribute("page", wordsPage);
        model.addAttribute("words", wordsPage.getContent());
        model.addAttribute("url", "/word/wordList");

        return "/word/wordList";
    }
}

表示件数の設定

ページ単位に最大の表示件数を設定します。

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
      PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
      //ページ単位に表示する件数
      resolver.setFallbackPageable(new PageRequest(0, 5));
      argumentResolvers.add(resolver);
      super.addArgumentResolvers(argumentResolvers);
  }

}

最後にThymeleaf側の実装です。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8" />
        <link th:substituteby="common/header :: common_header"/>
        <title>Word List</title>
    </head>
    <body>
        <table border="1">
            <tr>
                <th>ID</th>
                <th>WORD</th>
                <th>MEANING</th>
                <th>EXAMPLE</th>
            </tr>
            <tr th:each="word:${words}">
                <td th:text="${word.id}"></td>
                <td th:text="${word.word}"></td>
                <td th:text="${word.meaning}"></td>
                <td th:text="${word.example}"></td>
            </tr>
        </table>

        <div th:fragment='paginationbar'>
            <ul>
                <li th:class="${page.first} ? 'disabled':''" style="display:inline">
                    <span th:if="${page.first}">←先頭</span>
                    <a th:if="${not page.first}" th:href="@{${url}(page=0)}">←先頭</a>
                </li>
                <li th:each='i : ${#numbers.sequence(0, page.totalPages-1)}' th:class="(${i}==${page.number})? 'active' : ''" style="display:inline">
                    <span th:if='${i}==${page.number}' th:text='${i+1}'>1</span>
                    <a th:if='${i}!=${page.number}' th:href="@{${url}(page=${i})}">
                        <span th:text='${i+1}'>1</span>
                    </a>
                </li>
                <li th:class="${page.last} ? 'disabled':''" style="display:inline">
                    <span th:if="${page.last}">末尾➝</span>
                    <a th:if="${not page.last}" th:href="@{${url}(page=(${page.totalPages}-1))}">末尾➝</a>
                </li>
            </ul>
        </div>
    </body>
</html>

カスタマイズ

以下のようにページ数が多いケースには、一部のページ番号だけを表示したい場合、Pageのラッパークラスを作成し、カスタマイズを行うことが可能です。
2017-12-08_092643.jpg

ラッパークラスの実装

public class PageWrapper<T> {
    public static final int MAX_PAGE_ITEM_DISPLAY = 5;
    private Page<T> page;
    private List<PageItem> items;
    private int currentNumber;
    private String url;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public PageWrapper(Page<T> page, String url){
        this.page = page;
        this.url = url;
        items = new ArrayList<PageItem>();

        currentNumber = page.getNumber() + 1;

        int start, size;
        if (page.getTotalPages() <= MAX_PAGE_ITEM_DISPLAY){
            start = 1;
            size = page.getTotalPages();
        } else {
            if (currentNumber <= MAX_PAGE_ITEM_DISPLAY - MAX_PAGE_ITEM_DISPLAY/2){
                start = 1;
                size = MAX_PAGE_ITEM_DISPLAY;
            } else if (currentNumber >= page.getTotalPages() - MAX_PAGE_ITEM_DISPLAY/2){
                start = page.getTotalPages() - MAX_PAGE_ITEM_DISPLAY + 1;
                size = MAX_PAGE_ITEM_DISPLAY;
            } else {
                start = currentNumber - MAX_PAGE_ITEM_DISPLAY/2;
                size = MAX_PAGE_ITEM_DISPLAY;
            }
        }

        for (int i = 0; i<size; i++){
            items.add(new PageItem(start+i, (start+i)==currentNumber));
        }
    }

    public List<PageItem> getItems(){
        return items;
    }

    public int getNumber(){
        return currentNumber;
    }

    public List<T> getContent(){
        return page.getContent();
    }

    public int getSize(){
        return page.getSize();
    }

    public int getTotalPages(){
        return page.getTotalPages();
    }

    public boolean isFirstPage(){
        return page.isFirst();
    }

    public boolean isLastPage(){
        return page.isLast();
    }

    public boolean isHasPreviousPage(){
        return page.hasPrevious();
    }

    public boolean isHasNextPage(){
        return page.hasNext();
    }

    public class PageItem {
        private int number;
        private boolean current;
        public PageItem(int number, boolean current){
            this.number = number;
            this.current = current;
        }

        public int getNumber(){
            return this.number;
        }

        public boolean isCurrent(){
            return this.current;
        }
    }
}

コントロールクラスの変更

Pageの代わりにラッパークラスを使います。

@Controller
public class MainController {

    @Autowired
    private WordService wordService;

    @RequestMapping("/word/register")
    public String wordRegister(WordForm wordForm) {
        wordService.addWord(wordForm);
        return "/word/wordRegister";
    }

    @RequestMapping(value="/word/wordList", method=RequestMethod.GET)
    public String getWordList(Model model, Pageable pageable) {
        Page<Word> wordPage = wordService.getAllWord(pageable);
        PageWrapper<Word> page = new PageWrapper<Word>(wordPage, "/word/wordList");
        model.addAttribute("page", page);
        model.addAttribute("words", page.getContent());

        return "/word/wordList";
    }
}

Thymeleaf

ページングの部分は以下のように変更します。

    ...

        <div th:fragment='paginationbar'>
            <ul class='pagination pagination-centered'>
                <li th:class="${page.firstPage}?'disabled':''" style="display:inline">
                    <span th:if='${page.firstPage}'>←先頭</span>
                    <a th:if='${not page.firstPage}' th:href='@{${page.url}(page=0,size=${page.size})}'>←先頭</a>
                </li>
                <li th:class="${page.hasPreviousPage}? '' : 'disabled'" style="display:inline">
                    <span th:if='${not page.hasPreviousPage}'>«</span>
                    <a th:if='${page.hasPreviousPage}' th:href='@{${page.url}(page=${page.number-2},size=${page.size})}'>«</a>
                </li>


                <li th:each='item : ${page.items}' th:class="${item.current}? 'active' : ''" style="display:inline">
                    <span th:if='${item.current}' th:text='${item.number}'>1</span>
                    <a th:if='${not item.current}' th:href='@{${page.url}(page=${item.number-1},size=${page.size})}'>
                    <span th:text='${item.number}'>1</span>
                    </a>
                </li>
                <li th:class="${page.hasNextPage}? '' : 'disabled'" style="display:inline">
                    <span th:if='${not page.hasNextPage}'>»</span>
                    <a th:if='${page.hasNextPage}' th:href='@{${page.url}(page=${page.number},size=${page.size})}'>»</a>
                </li>
                <li th:class="${page.lastPage}? 'disabled' : ''" style="display:inline">
                    <span th:if='${page.lastPage}'>末尾➝</span>
                    <a th:if='${not page.lastPage}' th:href='@{${page.url}(page=${page.totalPages - 1},size=${page.size})}'>末尾➝</a>
                </li>
            </ul>
        </div>
    ...

参照:https://github.com/mtiger2k/pageableSpringBootDataJPA

85
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
KevinFQ

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
85
Help us understand the problem. What is going on with this article?