詳解Mybatis極其(最)簡(好)單(用)的一個分頁插件
注意:這篇博客已經(jīng)和當(dāng)前的分頁插件完全不一樣了,所以建議大家通過上面項目地址查看最新的源碼和文檔來了解。
以前為Mybatis分頁查詢發(fā)愁過,而且在網(wǎng)上搜過很多相關(guān)的文章,最后一個都沒采用。在分頁的地方完全都是手寫分頁SQL和count的sql,總之很麻煩。
后來有一段時間想從Mybatis內(nèi)部寫一個分頁的實現(xiàn),我對LanguageDriver寫過一個實現(xiàn),自動分頁是沒問題了,但是查詢總數(shù)(count)仍然沒法一次性解決,最后不了了之。
最近又要用到分頁,為了方便必須地寫個通用的分頁類,因此又再次參考網(wǎng)上大多數(shù)的Mybatis分頁代碼。
實際上在很早之前,有人在github上開源過一個實現(xiàn),支持MySQL,Oracle,sqlserver的,和上面這個參考的比較類似,考慮的更全面。但是我覺得太多類太麻煩了,所以自己實現(xiàn)了一個只有一個攔截器的類,實際上可以分為兩個類,其中一個類被我寫成靜態(tài)類放在了攔截器中,你也可以將Page類提取出來,方便使用Page。
先說實現(xiàn)方法,該插件只有一個類:PageHelper.Java
攔截器簽名為:
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
這里的簽名對整個實現(xiàn)和思想至關(guān)重要,首先我攔截prepare方法來改分頁SQL,來做count查詢。然后我攔截handleResultSets方法來獲取最后的處理結(jié)果,將結(jié)果放到Page對象中。
下面是修改分頁的代碼,是針對Oracle數(shù)據(jù)進行的修改,如果有用其他數(shù)據(jù)庫的,自己修改這里的代碼就可以。
/**
* 修改原SQL為分頁SQL
* @param sql
* @param page
* @return
*/
private String buildPageSql(String sql, Page page) {
StringBuilder pageSql = new StringBuilder(200);
pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
pageSql.append(sql);
pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());
pageSql.append(") where row_id > ").append(page.getStartRow());
return pageSql.toString();
}
之后在下面的setPageParameter方法中一個selelct count語句,這里也需要根據(jù)數(shù)據(jù)庫類型進行修改:
// 記錄總記錄數(shù)
String countSql = "select count(0) from (" + sql + ")";
為什么我不提供對各種數(shù)據(jù)庫的支持呢,我覺得沒必要,還有些數(shù)據(jù)庫不支持分頁,而且這個插件越簡單對使用的開發(fā)人員來說越容易理解,越容易修改。修改成自己需要的分頁查詢肯定不是問題。
最后上完整代碼(繼續(xù)看下去,下面還有使用方法):(點擊下載)
package com.mybatis.util;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.log4j.Logger;
import java.sql.*;
import java.util.List;
import java.util.Properties;
/**
* Mybatis - 通用分頁攔截器
* @author liuzh/abel533/isea
* Created by liuzh on 14-4-15.
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class PageHelper implements Interceptor {
private static final Logger logger = Logger.getLogger(PageHelper.class);
public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();
/**
* 開始分頁
* @param pageNum
* @param pageSize
*/
public static void startPage(int pageNum, int pageSize) {
localPage.set(new Page(pageNum, pageSize));
}
/**
* 結(jié)束分頁并返回結(jié)果,該方法必須被調(diào)用,否則localPage會一直保存下去,直到下一次startPage
* @return
*/
public static Page endPage() {
Page page = localPage.get();
localPage.remove();
return page;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
if (localPage.get() == null) {
return invocation.proceed();
}
if (invocation.getTarget() instanceof StatementHandler) {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
// 分離代理對象鏈(由于目標(biāo)類可能被多個攔截器攔截,從而形成多次代理,通過下面的兩次循環(huán)
// 可以分離出最原始的的目標(biāo)類)
while (metaStatementHandler.hasGetter("h")) {
Object object = metaStatementHandler.getValue("h");
metaStatementHandler = SystemMetaObject.forObject(object);
}
// 分離最后一個代理對象的目標(biāo)類
while (metaStatementHandler.hasGetter("target")) {
Object object = metaStatementHandler.getValue("target");
metaStatementHandler = SystemMetaObject.forObject(object);
}
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
//分頁信息if (localPage.get() != null) {
Page page = localPage.get();
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
// 分頁參數(shù)作為參數(shù)對象parameterObject的一個屬性
String sql = boundSql.getSql();
// 重寫sql
String pageSql = buildPageSql(sql, page);
//重寫分頁sql
metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
Connection connection = (Connection) invocation.getArgs()[0];
// 重設(shè)分頁參數(shù)里的總頁數(shù)等
setPageParameter(sql, connection, mappedStatement, boundSql, page);
// 將執(zhí)行權(quán)交給下一個攔截器
return invocation.proceed();
} else if (invocation.getTarget() instanceof ResultSetHandler) {
Object result = invocation.proceed();
Page page = localPage.get();
page.setResult((List) result);
return result;
}
return null;
}
/**
* 只攔截這兩種類型的
* StatementHandler
* ResultSetHandler
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
/**
* 修改原SQL為分頁SQL
* @param sql
* @param page
* @return
*/
private String buildPageSql(String sql, Page page) {
StringBuilder pageSql = new StringBuilder(200);
pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
pageSql.append(sql);
pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());
pageSql.append(") where row_id > ").append(page.getStartRow());
return pageSql.toString();
}
/**
* 獲取總記錄數(shù)
* @param sql
* @param connection
* @param mappedStatement
* @param boundSql
* @param page
*/
private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,
BoundSql boundSql, Page page) {
// 記錄總記錄數(shù)
String countSql = "select count(0) from (" + sql + ")";
PreparedStatement countStmt = null;
ResultSet rs = null;
try {
countStmt = connection.prepareStatement(countSql);
BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
boundSql.getParameterMappings(), boundSql.getParameterObject());
setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
rs = countStmt.executeQuery();
int totalCount = 0;
if (rs.next()) {
totalCount = rs.getInt(1);
}
page.setTotal(totalCount);
int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);
page.setPages(totalPage);
} catch (SQLException e) {
logger.error("Ignore this exception", e);
} finally {
try {
rs.close();
} catch (SQLException e) {
logger.error("Ignore this exception", e);
}
try {
countStmt.close();
} catch (SQLException e) {
logger.error("Ignore this exception", e);
}
}
}
/**
* 代入?yún)?shù)值
* @param ps
* @param mappedStatement
* @param boundSql
* @param parameterObject
* @throws SQLException
*/
private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
Object parameterObject) throws SQLException {
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler.setParameters(ps);
}
/**
* Description: 分頁
* Author: liuzh
* Update: liuzh(2014-04-16 10:56)
*/
public static class Page<E> {
private int pageNum;
private int pageSize;
private int startRow;
private int endRow;
private long total;
private int pages;
private List<E> result;
public Page(int pageNum, int pageSize) {
this.pageNum = pageNum;
this.pageSize = pageSize;
this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0;
this.endRow = pageNum * pageSize;
}
public List<E> getResult() {
return result;
}
public void setResult(List<E> result) {
this.result = result;
}
public int getPages() {
return pages;
}
public void setPages(int pages) {
this.pages = pages;
}
public int getEndRow() {
return endRow;
}
public void setEndRow(int endRow) {
this.endRow = endRow;
}
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 int getStartRow() {
return startRow;
}
public void setStartRow(int startRow) {
this.startRow = startRow;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
@Override
public String toString() {
return "Page{" +
"pageNum=" + pageNum +
", pageSize=" + pageSize +
", startRow=" + startRow +
", endRow=" + endRow +
", total=" + total +
", pages=" + pages +
'}';
}
}
}
使用該攔截器首先需要在Mybatis配置中配置該攔截器:
<plugins> <plugin interceptor="com.mybatis.util.PageHelper"></plugin> </plugins>
配置攔截器的時候需要注意plugins的位置,plugins位置順序如下:
properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?
最后是調(diào)用該方法的例子代碼(Service層):
@Override
public PageHelper.Page<SysLoginLog> findSysLoginLog(String loginIp,
String username,
String loginDate,
String exitDate,
String logerr,
int pageNumber,
int pageSize) throws BusinessException {
PageHelper.startPage(pageNumber,pageSize);
sysLoginLogMapper.findSysLoginLog(loginIp, username, loginDate, exitDate, logerr);
return PageHelper.endPage();
}
從上面可以看到使用該插件使用起來是很簡單的,只需要在查詢前后使用PageHelper的startPage和endPage方法即可,中間代碼的調(diào)用結(jié)果已經(jīng)存在于Page的result中,如果你在一個返回一個結(jié)果的地方調(diào)用PageHelper,返回的結(jié)果仍然是一個List,取第一個值即可(我想沒人會在這種地方這么用,當(dāng)然這樣也不出錯)。
另外在startPage和endPage中間的所有mybatis代碼都會被分頁,而且PageHelper只會保留最后一次的結(jié)果,因而使用時需要保證每次只在其中執(zhí)行一個mybatis查詢,如果有多個分頁,請多次使用startPage和endPage。
由于這里只提供了Oracle的實現(xiàn),所以我希望參考該分頁插件實現(xiàn)的其他數(shù)據(jù)庫的讀者也能將相應(yīng)的代碼開源。
項目地址:http://xiazai.jb51.net/201612/yuanma/Mybatis_PageHelper_jb51.zip
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java反射機制詳解_動力節(jié)點Java學(xué)院整理
Java 反射機制。通俗來講呢,就是在運行狀態(tài)中,我們可以根據(jù)“類的部分已經(jīng)的信息”來還原“類的全部的信息”。這篇文章給大家詳細介紹了java反射機制的知識,感興趣的朋友一起看看吧2017-06-06
三分鐘讀懂mybatis中resultMap和resultType區(qū)別
這篇文章主要給大家介紹了mybatis中resultMap和resultType區(qū)別的相關(guān)資料,resultType和resultMap都是mybatis進行數(shù)據(jù)庫連接操作處理返回結(jié)果的,需要的朋友可以參考下2023-07-07

