Mybatis实现分页

0. 什么时候会用到分页

在web开发过程中涉及到表格时,例如dataTable,就会产生分页的需求,通常我们将分页方式分为两种:前端分页和后端分页。

  • 前端分页
    一次性请求数据表格中的所有记录(ajax),然后在前端缓存并且计算count和分页逻辑,一般前端组件(例如dataTable)会提供分页动作。
    特点是:简单,很适合小规模的web平台;当数据量大的时候会产生性能问题,在查询和网络传输的时间会很长。

  • 后端分页
    在ajax请求中指定页码(pageNum)和每页的大小(pageSize),后端查询出当页的数据返回,前端只负责渲染。
    特点是:复杂一些;性能瓶颈在MySQL的查询性能,这个当然可以调优解决。一般来说,web开发使用的是这种方式。

通常我们说的也是后端分页。 MySQL对分页的支持
简单来说MySQL对分页的支持是通过limit子句。请看下面的例子。

offset是相对于首行的偏移量(首行是0),rows是返回条数。

# 每页10条记录,取第一页,返回的是前10条记录 
select * from tableA limit 0,10;
# 每页10条记录,取第二页,返回的是第11条记录,到第20条记录, 
select * from tableA limit 10,10; 

这里简单说一下,MySQL在处理分页的时候是这样的:

limit 1000,10 - 过滤出1010条数据,然后丢弃前1000条,保留10条。当偏移量大的时候,性能会有所下降。

limit 100000,10 - 会过滤10w+10条数据,然后丢弃前10w条。如果在分页中发现了性能问题,可以根据这个思路调优。

1. Mybatis实现分页的方式

1.1 分页的分类:

  1. 物理分页:只从数据库中查询当前页的数据
    优点:不占用很多内存 缺点:效率比价低(相比于逻辑分页)

  2. 逻辑分页:从数据库将所有记录查询出来,存储到内存中,展示当前页,然后数据再直接从内存中获取
    优点:效率高 缺点:占用内存比较高

大多数情况下,我们用的都是物理分页。
物理分页:

  • 1.直接用jdbc完成:使用滚动结果集
  • 优点:跨数据库     缺点:性能低
    
  • 2.使用数据库本身提供的分页操作:使用每一个数据库特定的分页函数
  • 优点:性能高       缺点:不能跨数据库
    

2. Mybatis具体实现分页

关于分页,基本上在后台查询数据的时候都会用到,我在做项目的时候也遇到需要实现分页,用到了一个非常简单的插件工具PageHeper。这里我以我的项目为例具体说下怎么使用,写的不好还请见谅。

2.1 引入分页插件的方式

引入汾阳王插件的方式有以下两种方式:
1)直接导入Jar包,用eclipse做开发的基本都是直接下载jar包,然后导入到WEB-INF/lib文件夹下。直接点击下面链接下载最新版即可。
插件下载
2)使用Maven项目资源管理器(推荐使用)
对于没有用过Maven的我分享一个学习资源。也是我学习的方式。慕课网项目管理利器——Maven
在pom.xml中添加如下依赖


    com.github.pagehelper
    pagehelper
    最新版本

2.2 配置拦截器插件

下面是我的项目中Mybatis的配置文件mybatis-config.xml




	
    
        
        
    
    
        
        
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
            
        
    

2.3 编写dao层接口(实际过程中已经写好了POJO实体类)

下面以我项目中的admin实体类为例:

package com.ldu.dao;

import com.ldu.pojo.Admin;

public interface AdminMapper {
	
	public Admin findAdmin(Long phone, String password);

	public Admin findAdminById(Integer id);

	public void updateAdmin(Admin admins);

}

(实际开发过程中可以吧service层接口也写了,因为dao层接口和service层接口是一样的。
这时候有人就会疑惑一样为什么还要写,代码都是重复的。当然这样设计是有他自己的道理的,单独独立出一个service接口层,是为了降低Sevice类的耦合性,服务于service.impl层)下面附上我的adminServiceImpl类:

package com.ldu.service.impl;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.ldu.dao.AdminMapper;
import com.ldu.pojo.Admin;
import com.ldu.service.AdminService;

@Service(value="adminService")
public class AdminServiceImpl implements AdminService {
	@Resource
	private AdminMapper am;

	@Override
	public Admin findAdmin(Long phone, String password) {
		// TODO Auto-generated method stub
		return am.findAdmin(phone,password);
	}

	@Override
	public Admin findAdminById(Integer id) {
		// TODO Auto-generated method stub
		return am.findAdminById(id);
	}

	@Override
	public void updateAdmin(Admin admins) {
		 am.updateAdmin(admins);
	}


	

}

2.4 编写POJO与SQL的映射文件mapper.xml

一般把该类映射文件放在src/mapper文件夹下,下面附上我的adminMapper.xml文件




  
    
    
    
    
    
    
  
  
  
   
	
	
	
	
    update admin set password=#{password,jdbcType=VARCHAR} where id=#{id,jdbcType=INTEGER}
  	
 
   

2.5 编写controller类

前面的流程一般的项目开发都一样,需要分页的话加一个拦截器Pageintercetor,改一下mybatis配置文件即可。
整个开发过程中最复杂的就是controller类的编写,下面附上我的代码片段:

	/*********************************************************
	 * 用户管理 1.查找所有用户  5.查询用户
	 * 
	 **********************************************************/

	/* 查找所有用户 */
	@RequestMapping(value = "/userList")
	@ResponseBody
	public ModelAndView getUserList(@RequestParam("pageNum") int pageNum) {
		ModelAndView modelAndView = new ModelAndView();
		int pageSize = 10;
		int total = userService.getUserNum();
		List rows = userService.getPageUser(pageNum, pageSize);
		UserGrid userGrid = new UserGrid();
		userGrid.setCurrent(pageNum);
		userGrid.setRowCount(pageSize);
		userGrid.setRows(rows);
		userGrid.setTotal(total);
		modelAndView.addObject("userGrid", userGrid);
		modelAndView.setViewName("admin/user/user_list");
		return modelAndView;
	}


/////我用到了XMLRootElement,
package com.ldu.util;

import javax.xml.bind.annotation.XmlRootElement;

import com.ldu.pojo.User;

import java.util.List;

/**
 * 
 */

@XmlRootElement
public class UserGrid {
    private int current;//当前页面号
    private int rowCount;//每页行数
    private int total;//总行数
    private List rows;

    public int getCurrent() {
        return current;
    }

    public void setCurrent(int current) {
        this.current = current;
    }

    public int getRowCount() {
        return rowCount;
    }

    public void setRowCount(int rowCount) {
        this.rowCount = rowCount;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public List getRows() {
        return rows;
    }

    public void setRows(List rows) {
        this.rows = rows;
    }
}

附上XMLRootElemen教程

说明可能会用到的方法:

1、PageHelper的优点是,分页和Mapper.xml完全解耦。实现方式是以插件的形式,对Mybatis执行的流程进行了强化,添加了总数count和limit查询。属于物理分页。

2、Page page = PageHelper.startPage(pageNum, pageSize, true); -
true表示需要统计总数,这样会多进行一次请求select count(0); 省略掉true参数只返回分页数据。

1)统计总数,(将SQL语句变为 select count(0) from xxx,只对简单SQL语句其效果,复杂SQL语句需要自己写)

Page page = PageHelper.startPage(1,-1);

long count = page.getTotal();

2)分页,pageNum - 第N页, pageSize - 每页M条数

A、只分页不统计(每次只执行分页语句)

PageHelper.startPage([pageNum],[pageSize]);

List pagelist = queryForList( xxx.class, "queryAll" , param);

//pagelist就是分页之后的结果

B、分页并统计(每次执行2条语句,一条select count语句,一条分页语句)适用于查询分页时数据发生变动,需要将实时的变动信息反映到分页结果上

Page page = PageHelper.startPage([pageNum],[pageSize],[iscount]);

List pagelist = queryForList( xxx.class , "queryAll" , param);

long count = page.getTotal();

//也可以 List pagelist = page.getList();  获取分页后的结果集

3)使用PageHelper查全部(不分页)

PageHelper.startPage(1,0);

List alllist = queryForList( xxx.class , "queryAll" , param);

4)PageHelper的其他API

String orderBy = PageHelper.getOrderBy();    //获取orderBy语句

Page page = PageHelper.startPage(Object params);

Page page = PageHelper.startPage(int pageNum, int pageSize);

Page page = PageHelper.startPage(int pageNum, int pageSize, boolean isCount);

Page page = PageHelper.startPage(pageNum, pageSize, orderBy);

Page page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable);    //isReasonable分页合理化,null时用默认配置

Page page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable, isPageSizeZero);   

//isPageSizeZero是否支持PageSize为0,true且pageSize=0时返回全部结果,false时分页,null时用默认配置

5)、默认值

//RowBounds参数offset作为PageNum使用 - 默认不使用

private boolean offsetAsPageNum = false;

//RowBounds是否进行count查询 - 默认不查询

private boolean rowBoundsWithCount = false;

//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果

private boolean pageSizeZero = false;

//分页合理化

private boolean reasonable = false;

//是否支持接口参数来传递分页参数,默认false

private boolean supportMethodsArguments = false;

2.6 编写JSP文件

在user_list.jsp文件中使用了bootstrap实现响应式页面设计,bootstrap教程

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
    String path = request.getContextPath();
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>








用户列表
	









	
	
	
	
	
手机: 昵称: QQ:
ID 手机号 昵称 QQ 创建时间 状态 操作
${item.id} ${item.phone} ${item.username} ${item.qq} ${item.createAt} 正常 禁用
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>



	后台管理系统
	
	
	
	
	
	
	


用户信息

导出数据为excel 添加用户
序号 手机号 姓名 QQ 开通时间 商品数量 用户权限 Commands

3. 什么时候会导致不安全的分页?

PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。

只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。

如果代码在进入 Executor 前发生异常,就会导致线程不可用,这属于人为的 Bug(例如接口方法和 XML 中的不匹配,导致找不到 MappedStatement 时), 这种情况由于线程不可用,也不会导致 ThreadLocal 参数被错误的使用。

但是如果你写出下面这样的代码,就是不安全的用法:

PageHelper.startPage(1, 10);
List list;
if(param1 != null){
    list = countryMapper.selectIf(param1);
} else {
    list = new ArrayList();
}

这种情况下由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。

上面这个代码,应该写成下面这个样子:

List list;
if(param1 != null){
    PageHelper.startPage(1, 10);
    list = countryMapper.selectIf(param1);
} else {
    list = new ArrayList();
}

这种写法就能保证安全。

如果你对此不放心,你可以手动清理 ThreadLocal 存储的分页参数,可以像下面这样使用:

List list;
if(param1 != null){
    PageHelper.startPage(1, 10);
    try{
        list = countryMapper.selectAll();
    } finally {
        PageHelper.clearPage();
    }
} else {
    list = new ArrayList();
}

这么写很不好看,而且没有必要。

你可能感兴趣的:(Mybatis)