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!

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