pring data jpa(SpringBoot)统一处理分页和排序

转载请注明出处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;
}

继承jpa

如上的想法,可以继承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 listBySQL(String sql);
    List listByHQL(String hql);

    @Transactional
    public void updateBySql(String sql, Object...args);
    @Transactional
    public void updateByHql(String hql, Object...args);
}

定义了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 + "]";
    }
}
  1. 创建一个类处理接收到的参数
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;
    }
}
  1. 创建一个过滤器,查看每HTTP请求中分页、排序的属性。交给SystemRequestHolder处理。
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() {}

}
  1. 对SimpleJpaRepository类中的6个findAll进行重写
  2. 自定义的接口实现了,需要把这个接口像原来父接口那样方便的使用。继承JpaRepositoryFactoryBean,把CustomRepositoryImpl设为接口的默认实现。
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(Classextends 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;
        }
    }

}
  1. 还需要有个配置文件。扫描包:”com.biboheart”。这个很重要。
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 {}

修改repository

修改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);
    }
}

测试

还是用原来的请求,加个排序上去。现在的排序需要请求方控制。

请求结果与上面的一致的。

总结

  1. 使用spring data jpa实现数据分页
  2. 自定义repository接口与实现,继承至JpaRepository

你可能感兴趣的:(java,SpringBoot)