Spring Boot + Thymeleafで画面のページング機能を簡単に実装できます。
完成後の画面イメージ
##検証環境
- 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のラッパークラスを作成し、カスタマイズを行うことが可能です。
###ラッパークラスの実装
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>
...