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的中对插件的实现采用了责任链模式。后面我们将分析具体的实现原理,欢迎交流。