1 概述
在我們使用Mybatis的時候希望對以映射語句進行攔截處理,這個時候就可以用到Mybatis的插件。接下來我們就來Mybatis中的插件是如何實現的,然後來實現分頁插件。
2 實現
MyBatis 允許使用插件來攔截的方法調用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
如果要實現Mybatis的插件,需要實現Mybatis的Interceptor 接口。下面我們就來看一下分頁插件的具體實現吧。
3 分頁插件
(1)插件核心代碼
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
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.ibatis.session.Configuration;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
/**
* 分頁插件
*
* @author: LIUTAO
* @Date: Created in 2018/11/6 11:26
* @Modified By:
*/
@Intercepts({
@Signature(type = StatementHandler.class,
method = "prepare",
args = {Connection.class})})
public class PagingPlugin implements Interceptor {
private Integer defaultPage;
private Integer defaultPageSize;
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler stmtHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(stmtHandler);
//判斷是否是查詢語句
if (checkIsSelect(metaStatementHandler)) {
return invocation.proceed();
}
BoundSql boundSql = stmtHandler.getBoundSql();
Object parameterObject = boundSql.getParameterObject();
//獲取到分頁數據
PageParams pageParams;
if (parameterObject instanceof PageParams) {
pageParams = (PageParams) parameterObject;
}else {
return invocation.proceed();
}
//獲取參數
Boolean useFlag = pageParams.getUseFlag();
if (!useFlag) {
return invocation.proceed();
}
Integer pageNum = pageParams.getPageNum() == null ? this.defaultPage : pageParams.getPageNum();
Integer pageSize = pageParams.getPageSize() == null ? this.defaultPageSize : pageParams.getPageSize();
Connection connection = (Connection) invocation.getArgs()[0];
int total = getTotal(connection, metaStatementHandler, boundSql);
int totalPage = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
pageParams.setTotalNums(total);
pageParams.setTotalPage(totalPage);
//檢查當前頁碼的有效性
checkPage(pageNum, totalPage);
//修改sql
changeSql(metaStatementHandler, pageNum, pageSize);
return invocation.proceed();
}
/**
* 判斷是否是查詢語句
* @param metaStatementHandler
* @return
*/
private boolean checkIsSelect(MetaObject metaStatementHandler) {
SqlCommandType sqlCommandType = (SqlCommandType) metaStatementHandler.getValue("delegate.mappedStatement.sqlCommandType");
if (!sqlCommandType.equals(SqlCommandType.SELECT)) {
return true;
}
return false;
}
@Override
public Object plugin(Object statementHandler) {
return Plugin.wrap(statementHandler, this);
}
@Override
public void setProperties(Properties properties) {
String strDefaultPage = properties.getProperty("defaultPage", "1");
String strDefaultPageSize = properties.getProperty("defaultPageSize", "5");
this.defaultPage = Integer.parseInt(strDefaultPage);
this.defaultPageSize = Integer.parseInt(strDefaultPageSize);
}
/**
* 檢測當前頁碼的有效性
*
* @param pageNum
* @param pageTotal
*/
private void checkPage(Integer pageNum, Integer pageTotal) {
if (pageNum > pageTotal) {
throw new PageException("pageNum is bigger than pageTotal!");
}
}
/**
* 修改當前查詢的sql
*
* @param metaObject
* @param page
* @param pageSize
*/
private void changeSql(MetaObject metaObject, int page, int pageSize) {
//獲取當前需要執行的SQL
String sql = (String) metaObject.getValue("delegate.boundSql.sql");
sql = sql.substring(0, sql.length() - 1);
String newSql = sql + " limit " + (page - 1) * pageSize + ", " + pageSize + ";";
//修改當前需要執行的sql
metaObject.setValue("boundSql.sql", newSql);
}
/**
* 獲取總數
*
* @param connection
* @param metaObject
* @param boundSql
* @return
*/
private int getTotal(Connection connection, MetaObject metaObject, BoundSql boundSql) throws SQLException {
//獲取當前mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
//獲取配置對象
Configuration configuration = mappedStatement.getConfiguration();
//獲取當前需要執行的sql
String sql = boundSql.getSql();
sql = sql.substring(0, sql.length() - 1);
String countSql = "select count(*) total from (" + sql + ") $proxytable ;";
//獲取攔截方法參數
PreparedStatement ps = null;
int total = 0;
try {
ps = connection.prepareStatement(countSql);
BoundSql countBoundSql = new BoundSql(configuration, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
ParameterHandler handler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql);
handler.setParameters(ps);
ResultSet resultSet = ps.executeQuery();
while (resultSet.next()) {
total = resultSet.getInt("total");
}
} catch (SQLException e) {
if (ps != null && ps.isClosed()) {
ps.close();
}
e.printStackTrace();
}
return total;
}
}
(2)分頁異常類
/**
* 分頁異常
*
* @author: LIUTAO
* @Date: Created in 2018/11/6 11:34
* @Modified By:
*/
public class PageException extends RuntimeException {
public PageException(String message) {
super(message);
}
}
(3)分頁數據封裝
/**
* 分頁數據封裝
*
* @author: LIUTAO
* @Date: Created in 2018/11/6 14:14
* @Modified By:
*/
public abstract class PageParams {
private Integer pageNum = 1;
private Integer pageSize = 5;
private Boolean useFlag = true;
private Integer totalNums;
private Integer totalPage;
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public Boolean getUseFlag() {
return useFlag;
}
public void setUseFlag(Boolean useFlag) {
this.useFlag = useFlag;
}
public Integer getTotalNums() {
return totalNums;
}
public void setTotalNums(Integer totalNums) {
this.totalNums = totalNums;
}
public Integer getTotalPage() {
return totalPage;
}
public void setTotalPage(Integer totalPage) {
this.totalPage = totalPage;
}
@Override
public String toString() {
return "PageParams{" +
"pageNum=" + pageNum +
", pageSize=" + pageSize +
", useFlag=" + useFlag +
", totalNums=" + totalNums +
", totalPage=" + totalPage +
'}';
}
}
(4)插件配置
<plugins>
<plugin interceptor="com.liutao.mybatis.util.PagingPlugin">
<property name="defaultPage" value="1" />
<property name="defaultPageSize" value="5" />
</plugin>
</plugins>
當我們使用的時候,只需要將我們的查詢參數繼承PageParams就可以實現數據的分頁查找。這裏順便提一下關於Mybatis的中對插件的實現採用了責任鏈模式。後面我們將分析具體的實現原理,歡迎交流。