整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Spring Boot:实现MyBatis分页

合概述

想必大家都有过这样的体验,在使用Mybatis时,最头痛的就是写分页了,需要先写一个查询count的select语句,然后再写一个真正分页查询的语句,当查询条件多了之后,会发现真的不想花双倍的时间写 count 和 select,幸好我们有 pagehelper 分页插件,pagehelper 是一个强大实用的 MyBatis 分页插件,可以帮助我们快速的实现MyBatis分页功能,而且pagehelper有个优点是,分页和Mapper.xml完全解耦,并以插件的形式实现,对Mybatis执行的流程进行了强化,这有效的避免了我们需要直接写分页SQL语句来实现分页功能。那么,接下来我们就来一起体验下吧。

实现案例

接下来,我们就通过实际案例来讲解如何使用pagehelper来实现MyBatis分页,为了避免重复篇幅,此篇教程的源码基于《Spring Boot:整合MyBatis框架》一篇的源码实现,读者请先参考并根据教程链接先行获取基础源码和数据库内容。

添加相关依赖

首先,我们需要在 pom.xml 文件中添加分页插件依赖包。

pom.xml

<!-- pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.5</version>
</dependency>

添加相关配置

然后在 application.yml 配置文件中添加分页插件有关的配置。

application.yml

# pagehelper   
pagehelper:
    helperDialect: mysql
    reasonable: true
    supportMethodsArguments: true
    params: count=countSql

编写分页代码

首先,在 DAO 层添加一个分页查找方法。这个查询方法跟查询全部数据的方法除了名称几乎一样。

SysUserMapper.java

package com.louis.springboot.demo.dao;

import java.util.List;
import com.louis.springboot.demo.model.SysUser;

public interface SysUserMapper {
    int deleteByPrimaryKey(Long id);

    int insert(SysUser record);

    int insertSelective(SysUser record);

    SysUser selectByPrimaryKey(Long id);

    int updateByPrimaryKeySelective(SysUser record);

    int updateByPrimaryKey(SysUser record);
    
    /**
     * 查询全部用户
     * @return
     */
    List<SysUser> selectAll();
    
    /**
     * 分页查询用户
     * @return
     */
    List<SysUser> selectPage();
}

然后在 SysUserMapper.xml 中加入selectPage的实现,当然你也可以直接用@Select注解将查询语句直接写在DAO代码,但我们这里选择写在XML映射文件,这是一个普通的查找全部记录的查询语句,并不需要写分页SQL,分页插件会拦截查询请求,并读取前台传来的分页查询参数重新生成分页查询语句。

SysUserMapper.xml

<select id="selectPage"  resultMap="BaseResultMap">
  select 
  <include refid="Base_Column_List" />
  from sys_user
</select>

服务层通过调用DAO层代码完成分页查询,这里统一封装分页查询的请求和结果类,从而避免因为替换ORM框架而导致服务层、控制层的分页接口也需要变动的情况,替换ORM框架也不会影响服务层以上的分页接口,起到了解耦的作用。

SysUserService.java

package com.louis.springboot.demo.service;
import java.util.List;
import com.louis.springboot.demo.model.SysUser;
import com.louis.springboot.demo.util.PageRequest;
import com.louis.springboot.demo.util.PageResult;

public interface SysUserService {

    /**
     * 根据用户ID查找用户
     * @param userId
     * @return
     */
    SysUser findByUserId(Long userId);

    /**
     * 查找所有用户
     * @return
     */
    List<SysUser> findAll();

    /**
     * 分页查询接口
     * 这里统一封装了分页请求和结果,避免直接引入具体框架的分页对象, 如MyBatis或JPA的分页对象
     * 从而避免因为替换ORM框架而导致服务层、控制层的分页接口也需要变动的情况,替换ORM框架也不会
     * 影响服务层以上的分页接口,起到了解耦的作用
     * @param pageRequest 自定义,统一分页查询请求
     * @return PageResult 自定义,统一分页查询结果
     */
    PageResult findPage(PageRequest pageRequest);
}

服务实现类通过调用分页插件完成最终的分页查询,关键代码是 PageHelper.startPage(pageNum, pageSize),将前台分页查询参数传入并拦截MyBtis执行实现分页效果。

SysUserServiceImpl.java

package com.louis.springboot.demo.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.louis.springboot.demo.dao.SysUserMapper;
import com.louis.springboot.demo.model.SysUser;
import com.louis.springboot.demo.service.SysUserService;
import com.louis.springboot.demo.util.PageRequest;
import com.louis.springboot.demo.util.PageResult;
import com.louis.springboot.demo.util.PageUtils;

@Service
public class SysUserServiceImpl implements SysUserService {
    
    @Autowired
    private SysUserMapper sysUserMapper;
    
    @Override
    public SysUser findByUserId(Long userId) {
        return sysUserMapper.selectByPrimaryKey(userId);
    }

    @Override
    public List<SysUser> findAll() {
        return sysUserMapper.selectAll();
    }
    
    @Override
    public PageResult findPage(PageRequest pageRequest) {
        return PageUtils.getPageResult(pageRequest, getPageInfo(pageRequest));
    }
    
    /**
     * 调用分页插件完成分页
     * @param pageQuery
     * @return
     */
    private PageInfo<SysUser> getPageInfo(PageRequest pageRequest) {
        int pageNum = pageRequest.getPageNum();
        int pageSize = pageRequest.getPageSize();
        PageHelper.startPage(pageNum, pageSize);
        List<SysUser> sysMenus = sysUserMapper.selectPage();
        return new PageInfo<SysUser>(sysMenus);
    }
}

在控制器SysUserController中添加分页查询方法,并调用服务层的分页查询方法。

SysUserController.java

package com.louis.springboot.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.louis.springboot.demo.service.SysUserService;
import com.louis.springboot.demo.util.PageRequest;

@RestController
@RequestMapping("user")
public class SysUserController {

    @Autowired
    private SysUserService sysUserService;
    
    @GetMapping(value="/findByUserId")
    public Object findByUserId(@RequestParam Long userId) {
        return sysUserService.findByUserId(userId);
    }
    
    @GetMapping(value="/findAll")
    public Object findAll() {
        return sysUserService.findAll();
    }
    
    @PostMapping(value="/findPage")
    public Object findPage(@RequestBody PageRequest pageQuery) {
        return sysUserService.findPage(pageQuery);
    }
}

分页查询请求封装类。

PageRequest.java

package com.louis.springboot.demo.util;
/**
 * 分页请求
 */
public class PageRequest {
    /**
     * 当前页码
     */
    private int pageNum;
    /**
     * 每页数量
     */
    private int pageSize;
    
    public int getPageNum() {
        return pageNum;
    }
    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }
    public int getPageSize() {
        return pageSize;
    }
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }
}

分页查询结果封装类。

PageResult.java

package com.louis.springboot.demo.util;
import java.util.List;
/**
 * 分页返回结果
 */
public class PageResult {
    /**
     * 当前页码
     */
    private int pageNum;
    /**
     * 每页数量
     */
    private int pageSize;
    /**
     * 记录总数
     */
    private long totalSize;
    /**
     * 页码总数
     */
    private int totalPages;
    /**
     * 数据模型
     */
    private List<?> content;
    public int getPageNum() {
        return pageNum;
    }
    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }
    public int getPageSize() {
        return pageSize;
    }
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }
    public long getTotalSize() {
        return totalSize;
    }
    public void setTotalSize(long totalSize) {
        this.totalSize = totalSize;
    }
    public int getTotalPages() {
        return totalPages;
    }
    public void setTotalPages(int totalPages) {
        this.totalPages = totalPages;
    }
    public List<?> getContent() {
        return content;
    }
    public void setContent(List<?> content) {
        this.content = content;
    }
}

分页查询相关工具类。

PageUtils.java

package com.louis.springboot.demo.util;
import com.github.pagehelper.PageInfo;

public class PageUtils {

    /**
     * 将分页信息封装到统一的接口
     * @param pageRequest 
     * @param page
     * @return
     */
    public static PageResult getPageResult(PageRequest pageRequest, PageInfo<?> pageInfo) {
        PageResult pageResult = new PageResult();
        pageResult.setPageNum(pageInfo.getPageNum());
        pageResult.setPageSize(pageInfo.getPageSize());
        pageResult.setTotalSize(pageInfo.getTotal());
        pageResult.setTotalPages(pageInfo.getPages());
        pageResult.setContent(pageInfo.getList());
        return pageResult;
    }
}

编译测试运行

启动应用,访问:localhost:8088/swagger-ui.html,找到对应接口,模拟测试,结果如下。

参数:pageNum: 1, pageSize: 5

参数:pageNum: 2, pageSize: 4

学习更多JAVA知识与技巧,关注与私信博主(学习)

xcel表格页围与页角这个地方就是设置页角的地方,可以增加页数,可以减少页数。这里可以输入公式加与减,但是输入乘号或者除号是不行的,只能输入加号和减号。

大家看一下,我把页码减到三,总页码加到二,然后大家看一下结果,第一页就变成负二了,总页数在二十六页基础上加了二变成二十八了。这个有什么用?如果想在表格预览看一下,想在任何一页的时候插入分页符,想从分页符那一页开始算作一,就需要这种设置。

举个例子,从这里随便找个地方插入一个分页符,预览一下,这就是半张了,第一张不想让它拍页码,只想让这一张拍页码,这样拍总页码还是二十六页的总页码,这个时候在预览的时候就可以从这设置了。

页面距记忆一般就是页眉页角,页码减个一,总页数减个一,这个时候再看,放大一下再看一下,袋就变成第一页二十五页了,一共二十五张,其中这是第一页,但是看这下边一共这是第二页,一共第二十六页,所以这种办法就可以实现插入分页符的时候按照自己想的页开始算页码。

springboot

设计目的就是为了加速开发,减少xml的配置。如果你不想写配置文件只需要在配置文件添加相对应的配置就能快速地启动的程序。

通用mapp

通用mapper只支持对单表的操作,对单表的增删改查,无需在mapper.xml写对应的sql语句,只需要我们调用相应的接口即可。

pagehelp

pagehelper主要是在对查询的数据进行一个分页查询。

  1. 首先在maven项目,在pom.xml中引入mapper和pagehelper的依赖
<!-- pagehelp -->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper-spring-boot-starter</artifactId>
  <version>1.2.3</version>
</dependency>
<!-- 通用mapper -->
<dependency>
  <groupId>tk.mybatis</groupId>
  <artifactId>mapper-spring-boot-starter</artifactId>
  <version>1.0.0</version>
</dependency>

2 新建一个mymapper.java文件,继承mapper接口

public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T>,ConditionMapper<T> {
  //FIXME 特别注意,该接口不能被扫描到,否则会出错
}

这个java文件不能和其它mapper放在一起,以免被扫描到。获取单表数据的操作都直接调用这个方法。

3 在配置文件上添加以后属性字段

#jdbc
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/news
spring.datasource.username=数据库用户名
spring.datasource.password=数据库密码
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.freemarker.request-context-attribute=request

#mapper  
mapper.mappers=com.imooc.springboot.mapper.util.MyMapper
mapper.not-empty=false
mapper.identity=MYSQL

#pagehelper
pagehelper.helper-dialect = mysql
pagehelper.reasonable = true
pagehelper.support-methods-arguments = true
pagehelper.params= count= countSql

上面的配置mapper.mappers 是第2步里面文件所在的路径。

4 添加了controller文件之后,由controller里面的方法去调用server里面的方法。虽然是有通用mapper方法,但是每次添加一个server方法之后都要添加对应的mapper方法,这样开发的也显得比较繁琐,所以我们需要一个通用server类,用这个类去调用第二步的方法就可以了。

public interface BaseService<T> {
       /**
	* 查询所有
	 * 
	 * @return 返回所有数据
	 */
	List<T> findAll();

	/**
	 * 添加
	 * 
	 * @param t   实体
	 *          
	 * @return
	 */
	int save(T t);

	/**
	 * 修改
	 * 
	 * @param t
	 *            实体
	 * @return
	 */
	int updateByPrimaryKey(T t);

	/**
	 * 根据主键删除
	 * 
	 * @param t   主键
	 *            
	 * @return
	 */
	int deleteByPrimaryKey(int t);
	
	/**
	 * 查询表格列表
	 * @param t 分页参数
	 * @return
	 */
	TableData<T> getTableData(PageBean pageBean);
}

上面只是封装基本增删改查的方法,后续可自行添加方法。
然后添加实现类

public abstract class BaseServiceImpl<T> implements BaseService<T> {
	@Autowired
	protected MyMapper<T> mapper;

	@Override
	public List<T> findAll() {
		return mapper.selectAll();
	}

	@Override
	public int save(T t) {
		return mapper.insert(t);
	}

	@Override
	public int updateByPrimaryKey(T t) {
		return mapper.updateByPrimaryKey(t);
	}

	@Override
	public int deleteByPrimaryKey(int t) {
		return mapper.deleteByPrimaryKey(t);
	}

	@Override
	public TableData<T> getTableData(PageBean bean) {
		int count = mapper.selectAll().size();
		if (count > 0) {
			PageHelper.startPage((bean.getOffset()/bean.getLimit()) + 1, bean.getLimit());
			List<T> list = this.findAll();
			return TableData.bulid(count, list);
		}

		return TableData.empty();
	}
}

注意:我用的编辑器是eclipse,如果用idea编辑器,这里可把abstract去掉。

然后添加对应的接口和实现类继承上面的接口和方法就可以了,比如添加一个newsserver 接口和newsserverImpl类

public interface NewsService extends BaseService<SysUser> {

}
@Service
public class NewsServiceImpl extends BaseServiceImpl<SysUser> implements NewsService{

}

5 为了减少数据库服务器的压力,一般我们查询数据的时候都会使用pagehelper进行分页查询,为了更加清晰的显示我们展示的数据,使用bootstrap table展示数据,bootstrap table获取数据有两种途径,一种是客户端模式,即获取全部数据之后,在前端进行分页展示。另外一种,也就是我们接下来要说的服务端模式:要获取的数据信息,比如获取数据页码,每一页数据的大小,都可以通过前端发送以上的参数向后台发请求,后台得到这些参数信息之后返回数据。
6 引入bootstrap table相关的js css文件之后,开始在网上找了一些资料之后发现很多都是要在前端页面添加如下繁琐的配置,

       $('#mytable').bootstrapTable({
                 //请求方法
                method: 'get',
                 //是否显示行间隔色
                striped: true,
                //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*)     
                cache: false,    
                //是否显示分页(*)  
                pagination: true,   
                 //是否启用排序  
                sortable: false,    
                 //排序方式 
                sortOrder: "desc",    
                //初始化加载第一页,默认第一页
                //我设置了这一项,但是貌似没起作用,而且我这默认是0,- -
                //pageNumber:1,   
                //每页的记录行数(*)   
                pageSize: 10,  
                //可供选择的每页的行数(*)    
                pageList: [10, 25, 50, 100],
                //这个接口需要处理bootstrap table传递的固定参数,并返回特定格式的json数据  
                url: "${contextPath}/mapper/getTableData",
                //默认值为 'limit',传给服务端的参数为:limit, offset, search, sort, order Else
                //queryParamsType:'',   
                ////查询参数,每次调用是会带上这个参数,可自定义                         
                queryParams: queryParams : function(params) {
                    var subcompany = $('#subcompany option:selected').val();
                    var name = $('#name').val();
                    return {
                          pageNumber: params.offset+1,
                          pageSize: params.limit,
                          companyId:subcompany,
                          name:name
                        };
                },
                //分页方式:client客户端分页,server服务端分页(*)
                sidePagination: "server",
                //是否显示搜索
                search: false,  
                //Enable the strict search.    
                strictSearch: true,
                //Indicate which field is an identity field.
                idField : "id",
                columns: [],
                pagination:true
            });

每次添加一个页面如果都要添加以上的配置信息也显得繁琐,不过bootstrap-table.js里面有个默认的配置,只需要修改里面的几个配置。

 contentType: 'application/json',//post请求头 application/x-www-form-urlencoded; charset=UTF-8'
 dataType: 'json',
 sidePagination: 'server', // 改成server       

当我们点击表格分页页码的时候,获取改变每页显示的页码时候,前端会自动调用queryParams()方法,我们需要将这些数据传递给后台,

       function queryParams(params) {
			var query={};
			query["limit"] = params.limit;//第几条数据开始
			query["offset"] = params.offset;//数据大小
			return query;
		}

6 配合上一步前端的分页,我们就需要使用pagehelp插件了,同样我们把这个分页的方法放在通用server类上,

 public TableData<T> getTableData(PageBean bean) {
        int count = mapper.selectAll().size();
        if (count > 0) {
            PageHelper.startPage((bean.getOffset()/bean.getLimit()) + 1, bean.getLimit());
            List<T> list = this.findAll();
            return TableData.bulid(count, list);
        }

        return TableData.empty();
    }

上面的pagehelper.startpage需要做一点改变,前端传过来的是显示第几条数据,但是startpage方法第一个参数是显示第几页的数据,所以做一个转换pageoffset/limit +1,然后在查询数据,需要注意的是,一定要将startpage方法方法查询数据语句的前一行,不能空行,或者换行。

附录:

github源码:https://github.com/jeremylai7/springboot-bootstrap.git

demo展示:https://www.jeremy7.cn/bootstrap/