一、使用拦截器需要注意的事项
使用的Mybatis版本是3.4.5,不同的版本略微有不同。
注意事项:
1、要拦截什么样的对象2、拦截对象的什么行为
3、什么时候拦截
分页拦截的原理:
在SQL预编译之前,将SQL修改,对SQL进行封装,加上分页的语句,再将修改过后的SQL语句塞回去。
拦截的方法如下:该方法位于org.apache.ibatis.executor.statement
二、MyBatis使用拦截器实现分页具体代码
下面的实现方式是通过使用Map的方式传递参数,把分页对象Page和实际的查询参数的对象分开传递,在实际开发的时候可以让查询对象继承Page,根据需要获取参数。
1、第一步:定义分页对象
package com.imooc.entity;
/**
* 分页对应的实体类
*/
public class Page {
/**
* 总条数
*/
private int totalNumber;
/**
* 当前第几页
*/
private int currentPage;
/**
* 总页数
*/
private int totalPage;
/**
* 每页显示条数
*/
private int pageNumber = 5;
/**
* 数据库中limit的参数,从第几条开始取
*/
private int dbIndex;
/**
* 数据库中limit的参数,一共取多少条
*/
private int dbNumber;
/**
* 根据当前对象中属性值计算并设置相关属性值
*/
public void count() {
// 计算总页数
int totalPageTemp = this.totalNumber / this.pageNumber;
int plus = (this.totalNumber % this.pageNumber) == 0 ? 0 : 1;
totalPageTemp = totalPageTemp + plus;
if(totalPageTemp <= 0) {
totalPageTemp = 1;
}
this.totalPage = totalPageTemp;
// 设置当前页数
// 总页数小于当前页数,应将当前页数设置为总页数
if(this.totalPage < this.currentPage) {
this.currentPage = this.totalPage;
}
// 当前页数小于1设置为1
if(this.currentPage < 1) {
this.currentPage = 1;
}
// 设置limit的参数
this.dbIndex = (this.currentPage - 1) * this.pageNumber;
this.dbNumber = this.pageNumber;
}
public int getTotalNumber() {
return totalNumber;
}
public void setTotalNumber(int totalNumber) {
this.totalNumber = totalNumber;
this.count();
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public int getPageNumber() {
return pageNumber;
}
public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
this.count();
}
public int getDbIndex() {
return dbIndex;
}
public void setDbIndex(int dbIndex) {
this.dbIndex = dbIndex;
}
public int getDbNumber() {
return dbNumber;
}
public void setDbNumber(int dbNumber) {
this.dbNumber = dbNumber;
}
}
2、第二步:定义SQL语句
注意:SQL语句中没有与分页相关的内容
<select id="queryMessageListByPage" parameterType="java.util.Map" resultMap="MessageResult">
select <include refid="columns"/> from MESSAGE
<where>
<if test="message.command != null and !"".equals(message.command.trim())">
and COMMAND=#{message.command}
</if>
<if test="message.description != null and !"".equals(message.description.trim())">
and DESCRIPTION like '%' #{message.description} '%'
</if>
</where>
order by ID
</select>
3、定义DAO接口注意:这里使用的是动态Mapper的实现方式
package com.imooc.dao;
import java.util.List;
import java.util.Map;
import com.imooc.bean.Message;
/**
* 与Message配置文件相对应的接口
*/
public interface IMessage {
/**
* 根据查询条件分页查询消息列表
*/
public List<Message> queryMessageListByPage(Map<String,Object> parameter);
}
4、第一步:定义拦截器实现Mybatis的org.apache.ibatis.plugin.Interceptor接口,具体内容详细见注释
package com.imooc.interceptor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Map;
import java.util.Properties;
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.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import com.imooc.entity.Page;
/**
* 分页拦截器,需要实现mybatis的拦截器接口
* 下面的注解解释:
* @Signature 用于指定要拦截的类中的某个方法(注意方法可能重载),
* 不同版本的mybatis该方法可能不一样
* type:用于指定拦截哪个接口
* method:用于指定拦截哪个方法
* args:指定被拦截的方法的参数
* 该类中的三个方法的执行顺序:
* 1、setProperties:获取配置文件中的值
* 2、plugin:对不需要进行分页的SQL语句进行过滤
* 3、intercept:执行拦截的逻辑
*/
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class, Integer.class})})
public class PageInterceptor implements Interceptor {
private String test;
/**
* 该方法拦截的是以ByPage结尾的方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取被拦截的对象,这里获取到的对象实现了StatementHandler接口
StatementHandler statementHandler =
(StatementHandler)invocation.getTarget();
//将statementHandler进行包装,进行包装之后,
//可以将metaObject看成是statementHandler
//注意forObject方法不同版本不同,这里使用的3.4.5版本,最后一个参数需要这么写
MetaObject metaObject = MetaObject.forObject(statementHandler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
//进行上述包装的目的是因为metaObject可以很容易的获取属性的值
//这里是根据StatementHandler的子类层次路由拿到MappedStatement,
//可以理解为固定写法
MappedStatement mappedStatement = (MappedStatement)metaObject
.getValue("delegate.mappedStatement");
// 配置文件中SQL语句的ID
String id = mappedStatement.getId();
if(id.matches(".+ByPage$")) {
BoundSql boundSql = statementHandler.getBoundSql();
// 原始的SQL语句
String sql = boundSql.getSql();
// 查询总条数的SQL语句
String countSql = "select count(*) from (" + sql + ")a";
//获取到参数的第一个参数
Connection connection = (Connection)invocation.getArgs()[0];
PreparedStatement countStatement = connection.prepareStatement(countSql);
ParameterHandler parameterHandler = (ParameterHandler)metaObject
.getValue("delegate.parameterHandler");
//设置查询总条数的SQL语句的总条数
parameterHandler.setParameters(countStatement);
ResultSet rs = countStatement.executeQuery();
//获取传递给SQL语句的参数
Map<?,?> parameter = (Map<?,?>)boundSql.getParameterObject();
//获取到page参数
Page page = (Page)parameter.get("page");
//设置page的总条数
if(rs.next()) {
page.setTotalNumber(rs.getInt(1));
}
// 改造后带分页查询的SQL语句
String pageSql = sql + " limit " + page.getDbIndex() + ","
+ page.getDbNumber();
//设置需要执行的分页SQL,这里采用OGNL的写法
metaObject.setValue("delegate.boundSql.sql", pageSql);
}
//释放主权,最终使用拦截器调用instantiateStatement
return invocation.proceed();
}
/**
* @param target 被拦截的对象,例如拦住一条SQL语句,
* 询问需不需要进行分页
* 该方法的作用是判断每条SQL需不需要进行分页
* 不需要分页的方法会经过plugin方法,
* 但是不会经过intercept方法
*/
@Override
public Object plugin(Object target) {
System.out.println(this.test);
/**
* 将类PageInterceptor比作代理公司,
* this就相当于该公司的业务员
*/
return Plugin.wrap(target, this);
}
/**
* 该方法的作用是拿到配置文件中的值
*/
@Override
public void setProperties(Properties properties) {
//获取配置文件中的值
this.test = properties.getProperty("test");
}
}
5、在SqlMapConfig.xml中进行拦截器的配置
<!-- 配置拦截器 -->
<plugins>
<!-- 配置分页拦截器 -->
<plugin interceptor="com.imooc.interceptor.PageInterceptor">
<!-- 配置属性,供拦截器获取值 -->
<property name="test" value="abc"/>
</plugin>
</plugins>
6、页面代码这里只提供分页处的代码。
<div class='page fix'>
共 <b>${page.totalNumber}</b> 条
<c:if test="${page.currentPage != 1}">
<a href="javascript:changeCurrentPage('1')" class='first'>首页</a>
<a href="javascript:changeCurrentPage('${page.currentPage-1}')" class='pre'>上一页</a>
</c:if>
当前第<span>${page.currentPage}/${page.totalPage}</span>页
<c:if test="${page.currentPage != page.totalPage}">
<a href="javascript:changeCurrentPage('${page.currentPage+1}')" class='next'>下一页</a>
<a href="javascript:changeCurrentPage('${page.totalPage}')" class='last'>末页</a>
</c:if>
跳至 <input id="currentPageText" type='text' value='${page.currentPage}' class='allInput w28' /> 页
<a href="javascript:changeCurrentPage($('#currentPageText').val())" class='go'>GO</a>
</div>