MyBatis 分页插件-PageHelper 笔记

分页插件(MyBatis-PageHelper)

微信公众号:Java后端————MyBatis 分页插件-PageHelper

PageHelper官方中文文档

整理于2020年2月16日

测试工具:java,eclipse

据说还有一个分页插件【sqlhelper】很强大()——sqlhelper官网——示例

  • MyBatis-PageHelper 已整理
  • SqlHelper 未整理

使用方法

1. 下载引入jar包

  1. PageHelper的maven依赖及插件配置
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>4.1.6</version>
    </dependency>
    
  2. 阿里仓库下载

    pagehelper-5.1.11.jar

    jsqlparser-3.0.jar

2. 配置拦截器插件

2.0 可能需要配置的参数:(当使用默认的dialect的情况下以下参数有效)

序号
参数名
默认值
适用情况
作用
1 offsetAsPageNum false 使用RowBounds方式分页(命名空间调用方式) 如果觉得 RowBounds中的两个参数 offset,limit 不如 pageNum,pageSize 容易理解, 你可以使用 offsetAsPageNum参数,将该参数设置为 true后,offset会当成 pageNum 使用limit 和 pageSize含义相同。
2 rowBoundsWithCount false 使用RowBounds方式分页(命名空间调用方式) 默认情况下不会进行count查询,如果你想在分页查询时进行count查询, 以及使用更强大的 PageInfo类,你需要设置该参数为 true。注: PageRowBounds想要查询总数也需要配置该属性为true
3 helperDialect 自动检测自动适配 自动检测当前的数据库链接,自动选择合适的分页方式。也可以制定设置(注意sqlServer2012需要手动指定)
4 reasonable false 分页合理化参数(防止参数不合法),设置为True时,,pageNum<=0时会查询第一页pageNum>pages(超过总数时),会查询最后一页,默认false时,直接根据参数进行查询。
5 supportMethodsArguments false 通过Mapper接口参数来传递分页参数(通常配合params使用) 支持通过Mapper接口参数来传递分页参数,分页插件会从查询方法的参数值中,自动根据下面 params配置的字段中取值,查找到合适的值时就会自动分页。
6 params pageNum=pageNum;
pageSize=pageSize;
count=countSql;
reasonable=reasonable;
pageSizeZero=pageSizeZero
通过Mapper接口参数来传递分页参数(同时supportMethodsArguments设置为True) 为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的话使用默认值,
7 pageSizeZero false 当该参数设置为 true时,如果 pageSize=0或者 RowBounds.limit = 0就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是```Page ``类型)。
8 autoRuntimeDialect false 配置了动态数据源,并且连接不同类型的数据库(根据需要配合closeConn使用) 设置为true时,允许在运行时根据多数据源自动识别对应方言的分页 (不支持自动选择sqlserver2012,只能使用sqlserver)。
这种情况下,你还需要特别注意closeConn参数,由于获取数据源类型会获取一个数据库连接,所以需要通过这个参数来控制获取连接后,是否关闭该连接。默认为true,有些数据库连接关闭后就没法进行后续的数据库操作。而有些数据库连接不关闭就会很快由于连接数用完而导致数据库无响应。所以在使用该功能时,特别需要注意你使用的数据源是否需要关闭数据库连接。
当不使用动态数据源而只是自动获取 helperDialect时,数据库连接只会获取一次,所以不需要担心占用的这一个连接是否会导致数据库出错,但是最好也根据数据源的特性选择是否关闭连接
9 closeConn true(关闭) 运行动态数据源或没有设置 helperDialect属性自动获取数据库类型时(根据需要配合autoRuntimeDialect使用) 当使用运行时动态数据源或没有设置 helperDialect属性自动获取数据库类型时,会自动获取一个数据库连接, 通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为false后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定。(配置autoRuntimeDialect使用)
10 aggregateFunctions (5.1.5+) 默认为所有常见数据库的聚合函数,允许手动添加聚合函数(影响行数) 默认为所有常见数据库的聚合函数,允许手动添加聚合函数(影响行数),所有以聚合函数开头的函数,在进行count转换时,会套一层。其他函数和列会被替换为count(0),其中count列可以自己配置。(不了解——具体待补充)

2.1 在 MyBatis 配置(mybatis.xml) 中配置拦截器插件(官网复制)

<!-- 在<configuration></configuration>标签里面, (?表示零次或一次匹配前面的字符或子表达式)
    plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
    properties?, settings?, 
    typeAliases?, typeHandlers?, 
    objectFactory?,objectWrapperFactory?, 
    plugins?, 
    environments?, databaseIdProvider?, mappers?
-->
<plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 使用下面的方式配置参数,根据具体需要配置上面的参数 -->
        <property name="param1" value="value1"/>
	</plugin>
</plugins>

2. 在 Spring(整合mybatis) 配置文件(applicationContext.xml)中配置拦截器插件(官网复制)

<!-- SqlSessionFactory -->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource"></property>
	<!-- pojo别名 -->
	<property name="typeAliasesPackage" value="com.pojo"></property>
	<!-- pagehelper拦截器插件配置 -->
	<property name="plugins">
    	<array>
			<bean class="com.github.pagehelper.PageInterceptor">
				<property name="properties">
					<!--使用下面的方式配置参数,一行配置一个 -->
					<value>
				 		reasonable=true
					</value>
				</property>
			</bean>
		</array>
	</property>
</bean>

3. 使用示例

3.1 mybatis(无spring)使用

3.1.1 导入jar包(省略不贴了)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FBD3qcuR-1581942804504)(image/project_jie_gou20200217.png)]

3.1.2 配置拦截器(参考上面的第2步配置拦截器的两种方式)

mybatis.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<settings>
		<setting name="logImpl" value="LOG4J"/>
	</settings>
	<typeAliases>
		<package name="com.pojo"/>
	</typeAliases>
	<!-- 
	    plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
	    properties?, settings?, 
	    typeAliases?, typeHandlers?, 
	    objectFactory?,objectWrapperFactory?, 
	    plugins?, 
	    environments?, databaseIdProvider?, mappers?
	-->
	<plugins>
	    <!-- com.github.pagehelper为PageHelper类所在包名 -->
	    <plugin interceptor="com.github.pagehelper.PageInterceptor">
	        <!-- 使用下面的方式配置参数,后面会有所有的参数介绍-->
	        <!-- helperDialect:分页插c件会自动检测当前的数据库链接,自动选择合适的分页方式。也可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby -->
	        <!-- 除了SqlServer2012 其它数据库可写可不写 -->
	        <property name="helperDialect" value="mysql"/>
	        <!-- reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。 -->
	        <property name="reasonable" value="true"/>
	        <!-- supportMethodsArguments和params: 使用参数方式(mapper参数中添加pageNum和pageSize)时需要设置 下面的两个参数
	        supportMethodsArguments:支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。
	        params:为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 
				默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。
	        -->
	        <property name="supportMethodsArguments" value="true"/>
	        <property name="params" value="pageNum=pageNumKey;pageSize=pageSizeKey;"/>

	        <!-- 前提是使用 RowBounds 方式,根据需要才可以配置下面的两个参数(否则无效) 
	        offsetAsPageNum:默认值为 false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页。
	        rowBoundsWithCount:默认值为false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时,使用 RowBounds 分页会进行 count 查询。 -->
            <!-- 
	        <property name= "offsetAsPageNum" value="true"></property>
	        <property name= "rowBoundsWithCount" value="true"></property>
	        -->
		</plugin>
	</plugins>
	<environments default="default">
		<environment id="default">
			<transactionManager type="JDBC"></transactionManager>
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver"/>
				<property name="url" value="jdbc:mysql://localhost:3306/dbtest?useSSL=false"/>
				<property name="username" value="root"/>
				<property name="password" value="root"/>
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<package name="com.mapper"/>
	</mappers>
</configuration>

方法一、 RowBounds方式的调用 传入参数(new RowBounds(pageStart-1,pageSize))_推荐

分页插件检测到使用了RowBounds参数时,就会对该查询进行物理分页。(mybatis.xml可能需要配置的参数[offsetAsPageNum,rowBoundsWithCount]
LogMapper.java

public interface LogMapper {
	@Select("select * from log")
	List<Log> selByPageHelper1();
	
	@Select("select * from log")
	List<Log> selByPageHelper2(RowBounds rowBounds);
}

LogServiceTest.java

public class LogServiceTest{
    public static void main(String[] args) throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
		SqlSessionFactory factroy = new SqlSessionFactoryBuilder().build(is);
		SqlSession session = factroy.openSession();
		LogMapper mapper = session.getMapper(LogMapper.class);
		
		//方式一:通过命名空间调用(获取到的是从数据第一条开始,10条数据)  new RowBounds(pageStart-1,pageSize)
	    List<Log> list = session.selectList("com.mapper.LogMapper.selByPageHelper1", null,new RowBounds(0, 10));
        System.out.println(list);
        //方式二:mapper接口增加RowBounds参数
        List<Log> list2 = mapper.selByPageHelper2(new RowBounds(0,10));
        System.out.println(list2);
    }
}

方法二、 PageHelper.startPage(paegNum,pageSize,orderBy) 静态方法调用————常用_推荐

注意调用静态方法分页可以排序

String orderBy = "字段 desc";
PageHelper.startPage(paegNum,pageSize,orderBy);

PageHelper.startPage(1, 10);List<User> list = userMapper.selectIf(param1);

可能需要配置的两个参数[supportMethodsArguments,params]

紧跟着的第一个select方法会被分页,后面的select不会被分页,除非再次调用PageHelper.startPage。注意:此处可能出现不安全调用。

PageHelper.startPage(1, 10);List<User> list = userMapper.selectIf(param1);是在一起的,不要出现前一句生产了分页参数,但是后一句并没有消费,等到其他的select时可能消费了这个分页参数,就会出现莫名其妙的分页效果

LogMapper.java

public interface LogMapper {
	@Select("select * from log")
	List<Log> selByPageHelper1();
}

LogServiceTest.java

public class LogServiceTest{
    public static void main(String[] args) throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
		SqlSessionFactory factroy = new SqlSessionFactoryBuilder().build(is);
		SqlSession session = factroy.openSession();
		LogMapper mapper = session.getMapper(LogMapper.class);
		
        // 方式一:通过startPage(PageNum,pageSize)————常用
        // 获取第一页,10条内容,默认查询总数count
        String orderBy = 排序字段 + " desc";//按照(数据库)排序字段 倒序 排序
        PageHelper.startPage(pageNum,pageSize,orderBy);
        //或者PageHelper.startPage(pageNum,pageSize);PageHelper.orderBy("字段 desc")
        // 紧跟着的第一个select方法会被分页,后面的不会被分页,除非再次调用PageHelper.startPage
        List<Log> list = mapper.selByPageHelper1();
        System.out.println(list);
        
        // 也可以使用PageInfo对结果进行包装
		// PageInfo有2个构造方法:(list),(list,navigatePages页码数量)

		PageInfo<Log> pi = new PageInfo<Log>(list);
		System.out.println(pi);
		// 通过pi.getList()可以查出查到的数据集合,通过get(索引)即可得单个数据。
		
		//在这里封装数据时可以传参修改navigatePages(int类型导航页码数)
		// 如果页码数量大于总页数,那么getNavigatepageNums()也只是只有存在的页码
		PageInfo<Log> pi = new PageInfo<Log>(list,navigatePages);
		
		// 方式二:通过startPage(request)  request: url?pageNum=1&pageSize=10
		// 支持 ServletRequest,Map,POJO 对象,需要配合 params 参数(@Param()为mapper中的方法参数起名字)
		// 暂时没有进行测试
		PageHelper.startPage(request);
		List<Log> list = mapper.selByPageHelper1();
/*
PageInfo.toString():
PageInfo{
pageNum=1, //当前页
pageSize=2,//每页显示的条数
size=2,//该页条数
startRow=1,//从第几条开始
endRow=2, //到第几条结束
total=5, //总共有多少条
pages=3,//总共的页数
list=Page{count=true, pageNum=1, pageSize=2, startRow=0, endRow=2, total=5, pages=3, reasonable=false, pageSizeZero=false}
[com.zhiyou.clg.bean.User@52bf72b5, com.zhiyou.clg.bean.User@37afeb11], //当前页的数据  
prePage=0, //上一页
nextPage=2, //下一页
isFirstPage=true,//是否为第一页
isLastPage=false, hasPreviousPage=false, hasNextPage=true, 
navigatePages=8,//每页显示的页码个数(导航页码数默认为8可以传参修改)
navigateFirstPage=1,
navigateLastPage=3, 
navigatepageNums=[1, 2, 3]//页码的个数
}
*/
    }
}

方法三、 使用参数方式

想要使用参数方式,需要配置 supportMethodsArguments=true,(通过 Mapper 接口参数来传递分页参数)
根据需要配置的两个参数[supportMethodsArguments,params]

LogMapper.java

public interface LogMapper {
    
    @Select("select * from log")
    List<User> selByPageHelper1(//查看mybati.xml中的params的参数配置
        @Param("pageNumKeyKey") int pageNum, 
        @Param("pageSizeKeyKey") int pageSize);
    // 当调用这个方法时,由于同时发现了 pageNumKey 和 pageSizeKey 参数,这个方法就会被分页。params 提供的几个参数都可以这样使用。
    // 除了上面这种方式外,如果 User 对象中包含这两个参数值,也可以有下面的方法:参数对象
    @Select("select * from log")
    List<User> selByPageHelper1(User user);
    // 当从 User 中同时发现了 pageNumKey 和 pageSizeKey 参数,这个方法就会被分页。
    // 注意:pageNum 和 pageSize 两个属性同时存在才会触发分页操作,在这个前提下,其他的分页参数才会生效。
}

LogServiceTest.java

public class LogServiceTest{
    public static void main(String[] args) throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
		SqlSessionFactory factroy = new SqlSessionFactoryBuilder().build(is);
		SqlSession session = factroy.openSession();
		LogMapper mapper = session.getMapper(LogMapper.class);
		
		//普通的传参
		List<Log> list = mapper.selByPageHelper1(pageNum, pageSize);
		System.out.println(list);		
		// user对象属性包含分页参数pageNum和pageSize
		List<Log> list = mapper.selByPageHelper1(user;
		System.out.println(list);		
    }
}

第四种:ISelect 接口方式

LogMapper.java

public interface LogMapper {
	@Select("select * from log")
	List<Log> selByPageHelper1();
}

LogServiceTest.java

public class LogServiceTest{
    public static void main(String[] args) throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
		SqlSessionFactory factroy = new SqlSessionFactoryBuilder().build(is);
		SqlSession session = factroy.openSession();
		LogMapper mapper = session.getMapper(LogMapper.class);
		
		//1.0 jdk8 lambda用法  返回Page
		Page<Log> page = PageHelper.startPage(1, 10).doSelectPage(()-> mapper.selByPageHelper1());
		System.out.println(page);
		
		// 1.1 也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
		PageInfo pi = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
		    @Override
		    public void doSelect() {
		        mapper.selByPageHelper1();
		    }
		});
		System.out.println(pi);
		
		//1.2 对应的lambda用法
		PageInfo<Log> pi = PageHelper.startPage(1, 10).doSelectPageInfo(() -> mapper.selByPageHelper1());
		
		//2.1 count查询,返回一个查询语句的count数
		long total = PageHelper.count(new ISelect() {
		    @Override
		    public void doSelect() {
		        mapper.selByPageHelper1();
		    }
		});
		//2.2 lambda
		total = PageHelper.count(()->mapper.selByPageHelper1());
    }
}

PageHelper安全调用

  1. 使用 RowBoundsPageRowBounds参数方式是极其安全的(RowBounds继承自PageRowBounds)
  2. 使用参数方式是极其安全的
  3. 使用 ISelect是极其安全的

ISelect接口方式除了可以保证安全外,还特别实现了将查询转换为单纯的 count查询方式,这个方法可以将任意的查询方法,变成一个select count(*)的查询方法。

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

PageHelper方法使用了静态的ThreadLocal参数,分页参数和线程是绑定的。
只要你可以保证在 PageHelper方法调用后紧跟 MyBatis查询方法,这就是安全的。因为 PageHelperfinally代码段中自动清除了ThreadLocal存储的对象。
如果代码在进入Executor前发生异常,就会导致线程不可用,这属于人为的Bug(例如接口方法和 XML 中的不匹配,导致找不到 MappedStatement时),这种情况由于线程不可用,也不会导致 ThreadLocal参数被错误的使用。
但是如果你写出下面这样的代码,就是不安全的用法:

//比如下面的代码
PageHelper.startPage(1, 10);
List<User> list;
if(param1 != null){
    list = userMapper.selectIf(param1);
} else {
    list = new ArrayList<User>();
}
//这种情况下由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。
//当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,
//这就产生了莫名其妙的分页。
//上面这个代码,应该写成下面这个样子:
List<User> list;
if(param1 != null){
    PageHelper.startPage(1, 10);
    list = userMapper.selectIf(param1);
} else {
    list = new ArrayList<User>();
}     

最后补充一下:慎用Mybatis分页插件PageHelper!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章