转载请注明出处spring boot 实例之 数据分页–碧波之心简书
在前面已经完成了数据库连接。https://www.jianshu.com/p/9850582904fa。实现保存、删除、查询、列表。这章在上一节的基础上增加数据分页。最终统一处理HTTP请求的分页、排序。
顺带说明下,API接口和服务名称的规则,这样看到API路径也能判断出它实现了什么功能:
名称 | 值 | 描述 |
---|---|---|
保存 | save | 添加或修改资源信息,以save开头方法命名(如:savePassword),所有资源都有名为id,类型为整型的字段,如果id值为null/0会以添加进行操作,如果id值大于0,会以修改进行操作,API中有特殊说明的,以特殊说明为准 |
删除 | delete | 删除资源信息,以delete开头方法命名(如:deleteById) |
查询 | load | 单个资源查询,以load开头方法命名(如:loadByName) |
列表 | list/find | 列表资源查询,以list开头方法命名(如:listByMatch);对资源进行分页查询,以find开头方法命名(如:findByMatch),分页的参数系统统一处理,所有find开头的API都提供了分页和排序参数,在API中参数描述中不再说明,分页和排序的参数在本文下方说明 |
在UserService 接口中增加一个函数:find。此时文件内容如下:
package com.biboheart.demo.user.service;
import java.util.List;
import org.springframework.data.domain.Page;
import com.biboheart.brick.exception.BhException;
import com.biboheart.demo.user.domain.User;
public interface UserService {
/**
* 保存用户信息
*
* @param user
* 用户信息对象
* @return 保存成功后的用户信息或null
* @throws BhException 参数异常捕获
*/
public User save(User user) throws BhException;
/**
* 删除用户
*
* @param id
* 用户ID
* @return 返回删除成功的用户信息或null
*/
public User delete(Long id);
/**
* 查询用户信息
*
* @param id
* 用户ID
* @return 用户信息或null
*/
public User load(Long id);
/**
* 用户列表
*
* @return 返回用户对象列表
*/
public List list();
public Page find();
}
Page 是spring data jpa 的分页模型。数据结果如下:
{
"content": [{}], // 数据列表
"last": true, // 是否最后一页
"totalPages": 1, // 总页数
"totalElements": 1, // 数据总数
"sort": null, // 排序
"first": true, // 是否首页
"numberOfElements": 1, // 本页数据条数
"size": 10, // 每页长度
"number": 0 // 当前页序号
}
在UserService实现中实现find函数,这里用更新时间降序排列
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
...
略
...
@Override
public Page find(Integer page, Integer size) {
if (null == page) {
page = 0;
}
if (CheckUtils.isEmpty(size)) {
size = 10;
}
PageRequest pageable = PageRequest.of(page, size, Sort.Direction.DESC, "updateTime");
Page users = userRepository.findAll(pageable);
return users;
}
}
添加api接口
@RestController
public class UserController {
@Autowired
private UserService userService;
...
略
...
/**
* 用户列表(分页)
* @return
*/
@RequestMapping(value = "/userapi/user/find", method = {RequestMethod.POST, RequestMethod.GET})
public BhResponseResult> find(Integer pageOffset, Integer pageSize) {
Page users = userService.find(pageOffset, pageSize);
return new BhResponseResult<>(0, "success", users);
}
}
在API工具(postmain)中测试下接口
{
"code": 0,
"message": "success",
"result": {
"content": [
{
"id": 5,
"name": "王五",
"phone": null,
"birthday": null,
"createTime": 1528190939524,
"updateTime": 1528190939524
},
{
"id": 4,
"name": "李四",
"phone": null,
"birthday": null,
"createTime": 1528190931172,
"updateTime": 1528190931172
},
{
"id": 3,
"name": "张三",
"phone": null,
"birthday": null,
"createTime": 1528190922668,
"updateTime": 1528190922668
},
{
"id": 1,
"name": "biboheart",
"phone": null,
"birthday": null,
"createTime": 1528119696823,
"updateTime": 1528119696823
}
],
"pageable": {
"sort": {
"sorted": true,
"unsorted": false
},
"offset": 0,
"pageSize": 10,
"pageNumber": 0,
"paged": true,
"unpaged": false
},
"last": true,
"totalPages": 1,
"totalElements": 4,
"number": 0,
"size": 10,
"sort": {
"sorted": true,
"unsorted": false
},
"first": true,
"numberOfElements": 4
}
}
完成了分页功能,我在想,如果API中分页的参数pageOffset和pageSize能够统一处理,API接口中可以去掉这两个参数,只用关心实际业务的参数;而服务实现中也不用所有find中重复去写分页处理。还可能涉及到排序功能,多项排序,。这么多通用的处理能提取出来那该多好。就像下面这样就能得到同样的结果:
/**
* 用户列表(分页)
* @return
*/
@RequestMapping(value = "/userapi/user/find", method = {RequestMethod.POST, RequestMethod.GET})
public BhResponseResult> find() {
Page users = userService.find();
return new BhResponseResult<>(0, "success", users);
}
// 服务接口
public Page find();
// 实现
@Override
public Page find() {
Page users = userRepository.findAll((Pageable)null);
return users;
}
如上的想法,可以继承spring boot jpa的JpaRepository
package com.biboheart.demo.user.basejpa;
import java.io.Serializable;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.transaction.annotation.Transactional;
@NoRepositoryBean
@Transactional(readOnly=true)
public interface CustomRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
List
定义了interface,还需要实现它,继承之SimpleJpaRepository
package com.biboheart.demo.user.basejpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
public class CustomRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID>
implements CustomRepository<T, ID> {
private final EntityManager em;
public CustomRepositoryImpl(Class domainClass, EntityManager em) {
super(domainClass, em);
this.em = em;
}
@SuppressWarnings("unchecked")
@Override
public List listBySQL(String sql) {
return em.createNativeQuery(sql).getResultList();
}
@SuppressWarnings("unchecked")
@Override
public List listByHQL(String hql) {
return em.createQuery(hql).getResultList();
}
@Override
public void updateBySql(String sql, Object...args) {
Query query = em.createNativeQuery(sql);
int i = 0;
for(Object arg : args) {
query.setParameter(++i, arg);
}
query.executeUpdate();
}
@Override
public void updateByHql(String hql, Object...args) {
Query query = em.createQuery(hql);
int i = 0;
for(Object arg : args) {
query.setParameter(++i, arg);
}
query.executeUpdate();
}
}
把自定义的几个函数都实现了。
回到开始的问题。如果要把所有的分页、排序都统一处理。
1. 定义一个数据模型,用来接收请求的参数
package com.biboheart.demo.user.basejpa;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
public class SystemRequest implements Serializable {
private static final long serialVersionUID = -4168104962029946743L;
private static final int DEFAULT_PAGE_SIZE = 10;
private HttpServletRequest request;
private int pageSize;
private int pageOffset;
private String sort;
private String order;
public HttpServletRequest getRequest() {
return request;
}
public void setRequest(HttpServletRequest request) {
this.request = request;
}
public int getPageSize() {
return (0 >= pageSize) ? DEFAULT_PAGE_SIZE : pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getPageOffset() {
return (pageOffset <= 1) ? 1 : pageOffset;
}
public void setPageOffset(int pageOffset) {
this.pageOffset = pageOffset;
}
public String getSort() {
return sort;
}
public void setSort(String sort) {
this.sort = sort;
}
public String getOrder() {
return order;
}
public void setOrder(String order) {
this.order = order;
}
@Override
public String toString() {
return "SystemRequest [request=" + request + ", pageSize=" + pageSize + ", pageOffset=" + pageOffset + ", sort="
+ sort + ", order=" + order + "]";
}
}
package com.biboheart.demo.user.basejpa;
public class SystemRequestHolder {
private final static ThreadLocal systemRequesthreadLocal = new ThreadLocal();
public static void initRequestHolder(SystemRequest systemRequest) {
systemRequesthreadLocal.set(systemRequest);
}
public static SystemRequest getSystemRequest() {
return systemRequesthreadLocal.get();
}
public static void remove() {
systemRequesthreadLocal.remove();
}
public static Integer getRequestPageOffset() {
Integer pageOffset = SystemRequestHolder.getSystemRequest().getPageOffset();
if (pageOffset == null || pageOffset < 1) {
pageOffset = 1;
}
return pageOffset - 1;
}
public static Integer getRequestPageSize() {
Integer pageSize = SystemRequestHolder.getSystemRequest().getPageSize();
if (pageSize == null || pageSize < 0) {
pageSize = SystemRequest.DEFAULT_PAGE_SIZE;
}
return pageSize;
}
}
package com.biboheart.demo.user.basejpa;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
@Component("paginationFilter")
public class PaginationFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
int offset = 0;
int size = 10;
String offsetStr = request.getParameter("pageOffset");
String sizeStr = request.getParameter("pageSize");
if(null != offsetStr && !"".equals(offsetStr)){
offset = Integer.valueOf(offsetStr);
}
if(null != sizeStr && !"".equals(sizeStr)){
size = Integer.valueOf(sizeStr);
}
try {
SystemRequest systemRequest = new SystemRequest();
systemRequest.setOrder(request.getParameter("order"));
systemRequest.setPageOffset(offset);
systemRequest.setPageSize(size);
systemRequest.setRequest(request);
systemRequest.setSort(request.getParameter("sort"));
SystemRequestHolder.initRequestHolder(systemRequest);
chain.doFilter(request, response);
} finally {
SystemRequestHolder.remove();
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
package com.biboheart.demo.user.basejpa;
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
public class CustomRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable>
extends JpaRepositoryFactoryBean {
public CustomRepositoryFactoryBean(Class extends T> repositoryInterface) {
super(repositoryInterface);
}
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomRepositoryFactory(entityManager);
}
private static class CustomRepositoryFactory extends JpaRepositoryFactory {
@SuppressWarnings("unused")
private final EntityManager entityManager;
public CustomRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
@Override
@SuppressWarnings({"unchecked"})
protected extends Serializable> SimpleJpaRepository, ?> getTargetRepository(
RepositoryInformation information, EntityManager entityManager) {
return new CustomRepositoryImpl((Class) information.getDomainType(), entityManager);
}
@Override
protected Class> getRepositoryBaseClass(RepositoryMetadata metadata) {
return CustomRepositoryImpl.class;
}
}
}
package com.biboheart.demo.user.basejpa;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootConfiguration
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class, basePackages = "com.biboheart")
public class Config {}
修改UserRepository,继承至CustomRepository。API与接口的修改根据需要修改。
// userRepository:
package com.biboheart.demo.user.repository;
import com.biboheart.demo.user.basejpa.CustomRepository;
import com.biboheart.demo.user.domain.User;
public interface UserRepository extends CustomRepository {
}
// UserService
package com.biboheart.demo.user.service;
import java.util.List;
import org.springframework.data.domain.Page;
import com.biboheart.brick.exception.BhException;
import com.biboheart.demo.user.domain.User;
public interface UserService {
...
略
...
/**
* 用户列表
*
* @return 返回用户对象列表
*/
public List list();
public Page find();
}
// UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
...
略
...
@Override
public List list() {
List users = userRepository.findAll();
return users;
}
@Override
public Page find() {
Page users = userRepository.findAll((Pageable)null);
return users;
}
}
// UserController
@RestController
public class UserController {
@Autowired
private UserService userService;
...
略
...
/**
* 用户列表
* @return
*/
@RequestMapping(value = "/userapi/user/list", method = {RequestMethod.POST, RequestMethod.GET})
public BhResponseResult> list() {
List users = userService.list();
return new BhResponseResult<>(0, "success", users);
}
/**
* 用户列表(分页)
* @return
*/
@RequestMapping(value = "/userapi/user/find", method = {RequestMethod.POST, RequestMethod.GET})
public BhResponseResult> find() {
Page users = userService.find();
return new BhResponseResult<>(0, "success", users);
}
}
还是用原来的请求,加个排序上去。现在的排序需要请求方控制。
请求结果与上面的一致的。