24
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Springbootで検索画面の検索条件を保持しつつ、ページネーションを行う

Posted at

検索画面で、検索条件を指定するが、検索結果が多くて1ページに表示できないケースでは、検索結果に対してページネーションをしたいケースがあるので、それを実装してみます。

1.環境

eclipse 4.7.2
java 8
database postgreSQL10
build.gradle

	compile('org.springframework.boot:spring-boot-starter-data-jpa')
	compile('org.springframework.boot:spring-boot-starter-thymeleaf')
	compile('org.springframework.boot:spring-boot-starter-web')
	runtime('org.springframework.boot:spring-boot-devtools')
	runtime('org.postgresql:postgresql')
	compileOnly('org.projectlombok:lombok')
	providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
	testCompile('org.springframework.boot:spring-boot-starter-test')

2.ページネーション

他にもやり方はあると思いますが、Pageクラスと同じプロパティをもつBeanを定義してThymeleafに渡すようにしました。

(1)Bean

import lombok.Data;

@Data
public class Pagination {
	private boolean first;
	private boolean last;
	private int number;
	private int numberOfElements;
	private int size;
	private long totalElements;
	private int totalPages;
}

(2)Helper

Paginationオブジェクトを生成するクラスです。

PagenationHelper.java
import org.springframework.data.domain.Page;

public class PagenationHelper {
	public static Pagination createPagenation(Page pageObject) {
		Pagination pagination = new Pagination();
		pagination.setFirst(pageObject.isFirst());
		pagination.setLast(pageObject.isLast());
		pagination.setNumber(pageObject.getNumber());
		pagination.setNumberOfElements(pageObject.getNumberOfElements());
		pagination.setSize(pageObject.getSize());
		pagination.setTotalElements(pageObject.getTotalElements());
		pagination.setTotalPages(pageObject.getTotalPages());
		pagination.setNumberOfElements(pageObject.getNumberOfElements());
		return pagination;
	}
}

3.Entity

SpringbootのSpecificationで動的クエリに投稿したものと同じなので割愛する。

4.Repository

Pageableを使って、Pageを返します。

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

public interface EmployeeRepository extends JpaRepository<Employee, String>, JpaSpecificationExecutor<Employee> {
	public Page<Employee> findAll(Specification<Employee> and, Pageable pageable);
}

5.Service

Usernameでソートし5行毎にページングする設定にしてます。

EmployeeService.java
import static com.stone.sample.employee.EmployeeSpecifications.*;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class EmployeeService {

	private static final int PAGE_SIZE=5;

	@Autowired
	EmployeeRepository repository;
	public Page<Employee> search(int page, List<String> username, String empname) {
	    return repository.findAll(Specification
	        .where(usernameIn(username))
	        .and(empnameContains(empname))
	        ,PageRequest.of(page<=0?0:page, PAGE_SIZE, new Sort(
	                Sort.Direction.ASC, "username"))
	    );
	}
}

6.Specification

EmployeeSpecifications.java
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;

public class EmployeeSpecifications {
    public static Specification<Employee> empnameContains(final String empname) {
        return StringUtils.isEmpty(empname) ? null : new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.like(root.get("empname"), "%" + empname + "%");
            }
        };
    }
    public static Specification<Employee> usernameIn(final List<String> usernames) {
        return usernames==null||usernames.size()==0 ? null : new Specification<Employee>() {
            @Override
            public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                return cb.in(root.get("username")).value(usernames);
            }
        };
    }
}

7.Controller

SpecificationController.java

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;

import com.stone.sample.common.PagenationHelper;
import com.stone.sample.employee.Employee;
import com.stone.sample.employee.EmployeeService;
@Controller
@SessionAttributes(value = "form")
public class SpecificationController {

	@Autowired
	EmployeeService employeeService;

	@Autowired
	HttpSession session;

	private static final String SESSION_FORM_ID="searchForm";

	@RequestMapping(value="/", method = RequestMethod.GET)
	public ModelAndView get(Model model) {
		return search(new SearchForm(), model);
	}
	@RequestMapping(value="/pagenate", method = RequestMethod.GET)
	public ModelAndView pagenate(
			@RequestParam("page") int page,
			Model model) {
		SearchForm form = (SearchForm)session.getAttribute(SESSION_FORM_ID);
		form.setPage(page);
		return search(form, model);
	}

	@RequestMapping(value="/search", method = RequestMethod.POST)
	public ModelAndView search(
		  @ModelAttribute SearchForm form,
		  Model model) {
		session.setAttribute(SESSION_FORM_ID, form);
		List<String> usernames = new ArrayList<String>();
		if (!form.getUsername1().isEmpty()) usernames.add(form.getUsername1());
		if (!form.getUsername2().isEmpty()) usernames.add(form.getUsername2());
		if (!form.getUsername3().isEmpty()) usernames.add(form.getUsername3());
		Page<Employee> emps = employeeService.search(form.getPage(), usernames, form.getEmpname());
		ModelAndView mv=new ModelAndView();
		mv.setViewName("index");
		mv.addObject("form",form);
		mv.addObject("emplist",emps);
		mv.addObject("page",PagenationHelper.createPagenation(emps));
		return mv;
	}
}

"page"に、Paginationオブジェクトを渡します。
mv.addObject("page",PagenationHelper.createPagenation(emps));

8.form

SearchForm.java
import lombok.Data;
@Data
public class SearchForm {
	private int page;
	private String username1="";
	private String username2="";
	private String username3="";
	private String empname="";
}

9.index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
	<head>
		<title>Springboot</title>
		<meta charset="utf-8" />
		<style>
		  .pagination{
		    padding-left:0px;
		  }
			.pagination:after {
			  content: "";
			  display: block;
			  clear: both;
			}
			.page-item {
			  display: inline;
			  min-width: 30px;
			  text-align:center;
			  border:1px solid #EEEEEE;
			  float: left;
			}
		</style>
	</head>
	<body>
	  <h3>Specifinationで動的検索</h3>
	  <form th:action="@{/search}" th:object="${form}" method="post">
	    <div>
	    <label>ユーザID</label>
	    <input type="text" th:field="*{username1}" />
	    <input type="text" th:field="*{username2}" />
	    <input type="text" th:field="*{username3}" />
	    </div>
	    <div>
	    <label>ユーザ名</label>
	    <input type="text" th:field="*{empname}" />
	    </div>
	    <input type="submit" value="検索"/>
	  </form>
    <table border="1">
        <tr>
        <th>username</th>
        <th>empname</th>
        </tr>
        <tr th:each="emp : ${emplist}">
            <td th:text="${emp.username}"></td>
            <td th:text="${emp.empname}"></td>
        </tr>
    </table>
    <form th:action="@{/pagenate}" th:object="${page}" method="get">
    <div class="float-right">
      <div>
        <span th:text="${page.number+1}"></span>
	<span>/</span>
	<span th:text="${page.totalPages}"></span>
	<span>頁を表示</span>
      </div>
      <div th:fragment='paginationbar'>
        <ul>
          <li class="page-item">
              <span th:if="${page.first}">←先頭</span>
            <a th:if="${not page.first}" th:href="@{${url}(page=0)}">←先頭</a>
          </li>
          <li class="page-item" th:each='i : ${#numbers.sequence(0, page.totalPages-1)}'>
            <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 class="page-item">
            <span th:if="${page.last}">末尾➝</span>
            <a th:if="${not page.last}" th:href="@{${url}(page=(${page.totalPages}-1))}">末尾➝</a>           </li>
        </ul>
       </div>
     </div>
   </form>
  </body>
</html>

1画面に検索実行用とページングをするための2つのformとすることで、それぞれのURLで処理できるようにしています。

10.確認

image.png

24
32
0

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
24
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?