Mybatis 分頁詳解



/**
*
* 分頁攔截器,用於攔截需要進行分頁查詢的操作,然後對其進行分頁處理。


*
*/
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class,Integer.class})})
public class PageInterceptor implements Interceptor {
private String dialect = ""; //數據庫方言

private Log log = LogFactory.getLog(PageInterceptor.class);

@Override
public Object intercept(Invocation invocation) throws Throwable {
if(invocation.getTarget() instanceof RoutingStatementHandler){
RoutingStatementHandler statementHandler = (RoutingStatementHandler)invocation.getTarget();
StatementHandler delegate = (StatementHandler) CommonUtil.getFieldValue(statementHandler, "delegate");
BoundSql boundSql = delegate.getBoundSql();
Object obj = boundSql.getParameterObject();
if (obj instanceof PageDto) {
PageDto page = (PageDto) obj;
//獲取delegate父類BaseStatementHandler的mappedStatement屬性
MappedStatement mappedStatement = (MappedStatement)CommonUtil.getFieldValue(delegate, "mappedStatement");
//攔截到的prepare方法參數是一個Connection對象
Connection connection = (Connection)invocation.getArgs()[0];
//獲取當前要執行的Sql語句
String sql = boundSql.getSql();
//給當前的page參數對象設置總記錄數
this.setTotalRecord(page, mappedStatement, connection);
//給當前的page參數對象補全完整信息
//this.setPageInfo(page);
//獲取分頁Sql語句
String pageSql = this.getPageSql(page, sql);
//設置當前BoundSql對應的sql屬性為我們建立好的分頁Sql語句
CommonUtil.setFieldValue(boundSql, "sql", pageSql);
}
}
return invocation.proceed();
}

/**
* 給當前的參數對象page設置總記錄數
*
* @param page Mapper映射語句對應的參數對象

* @param mappedStatement Mapper映射語句
* @param connection 當前的數據庫連接
*/
private void setTotalRecord(PageDto page, MappedStatement mappedStatement, Connection connection) throws Exception{
//獲取對應的BoundSql
BoundSql boundSql = mappedStatement.getBoundSql(page);
//獲取對應的Sql語句
String sql = boundSql.getSql();
//獲取計算總記錄數的sql語句
String countSql = this.getCountSql(sql);
//通過BoundSql獲取對應的參數映射
List parameterMappings = boundSql.getParameterMappings();
//利用Configuration、查詢記錄數的Sql語句countSql、參數映射關係parameterMappings和參數對象page建立查詢記錄數對應的BoundSql對象。
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
//通過mappedStatement、參數對象page和BoundSql對象countBoundSql建立一個用於設定參數的ParameterHandler對象
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
//通過connection建立一個countSql對應的PreparedStatement對象。
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement(countSql);
//通過parameterHandler給PreparedStatement對象設置參數
parameterHandler.setParameters(pstmt);
//執行獲取總記錄數的Sql語句。
rs = pstmt.executeQuery();
if (rs.next()) {
int totalRecord = rs.getInt(1);
//給當前的參數page對象設置總記錄數
page.setTotalRecord(totalRecord);
}
} catch (SQLException e) {
log.error(e);
throw new SQLException();
} finally {

try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
} catch (SQLException e) {
log.error(e);
throw new SQLException();
}
}
}

/**
* 根據原Sql語句獲取對應的查詢總記錄數的Sql語句
* @param sql 原sql
* @return 查詢總記錄數sql
*/
private String getCountSql(String sql) {
int index = new String(sql).toLowerCase().indexOf("from");
return "select count(*) " + sql.substring(index);
}

/**
* 給page對象補充完整信息
*
* @param page page對象
*/
private void setPageInfo(PageDto page) {
Integer totalRecord = page.getTotalRecord();
Integer pageNo = page.getPageNo();
Integer rows = page.getRows();

//設置總頁數
Integer totalPage;
if (totalRecord > rows) {
if (totalRecord % rows == 0) {
totalPage = totalRecord / rows;
} else {
totalPage = 1 + (totalRecord / rows);
}
} else {
totalPage = 1;
}
page.setTotalPage(totalPage);

//跳轉頁大於總頁數時,默認跳轉至最後一頁

if (pageNo > totalPage) {
pageNo = totalPage;
page.setPageNo(pageNo);
}

//設置是否有前頁
if(pageNo <= 1) {
page.setHasPrevious(false);
} else {
page.setHasPrevious(true);
}

//設置是否有後頁
if(pageNo >= totalPage) {
page.setHasNext(false);
} else {
page.setHasNext(true);
}
}

/**
* 根據page對象獲取對應的分頁查詢Sql語句
* 其它的數據庫都 沒有進行分頁
*
* @param page 分頁對象
* @param sql 原sql語句
* @return 分頁sql
*/
private String getPageSql(PageDto page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql);
if ("mysql".equalsIgnoreCase(dialect)) {
//int offset = (page.getPageNo() - 1) * page.getRows();
sqlBuffer.append(" limit ").append(page.getOffset()).append(",").append(page.getRows());
return sqlBuffer.toString();
}
return sqlBuffer.toString();
}

/**
* 攔截器對應的封裝原始對象的方法
*/
@Override
public Object plugin(Object arg0) {

if (arg0 instanceof StatementHandler) {
return Plugin.wrap(arg0, this);
} else {
return arg0;
}
}

/**
* 設置註冊攔截器時設定的屬性
*/
@Override
public void setProperties(Properties p) {

}

public String getDialect() {
return dialect;
}

public void setDialect(String dialect) {
this.dialect = dialect;
}

}

重點講解:

  1. @Intercept註解中的@Signature中標示的屬性,標示當前攔截器要攔截的那個類的那個方法,攔截方法的傳入的參數
  2. 首先要明白,Mybatis是對JDBC的一個高層次的封裝。而JDBC在完成數據操作的時候必須要有一個陳述對象。而陳述對應的SQL語句是在是在陳之前產生的。所以我們的思路就是在生成報表之前對SQL進行下手。更改SQL語句成我們需要的!
  3. 對於MyBatis的,其聲明的英文生成在RouteStatementHandler中。所以我們要做的就是攔截這個處理程序的prepare方法!然後修改的Sql語句!
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 其實就是代理模式!
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");
String sql= delegate.getBoundSql().getSql();
return invocation.proceed();
}
  1. 我們知道利用Mybatis查詢一個集合時傳入Rowbounds對象即可指定其Offset和Limit,只不過其沒有利用原生sql去查詢罷了,我們現在做的,就是通過攔截器拿到這個參數,然後織入到SQL語句中,這樣我們就可以完成一個物理分頁!

註冊攔截器

在Spring文件中引入攔截器

 




...






分頁定義的接口:

 List selectForSearch(PageDto pageDto);

客戶端調用如下:

 PageDto pageDto = new PageDto<>();
Student student =new Student();
student.setId(1234);
student.setName("sky");
pageDto.setSearchCondition(student);


分享到:


相關文章: