85
91

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-01-21

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
91
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
85
91