背景
在平时的后端开发工作中,经常会使用到分页查询的功能,往往这时候就要把pageNum和pageSize这样的参数传入到dao中,而传回给前端的数据中还会包括有totalNum和totalPage这样的值,就得分开几条sql去查询。
有没有好的方法可以自动进行这些繁琐而多余的操作呢?mybatis为我们提供了插件这样的方式,插件实际上也就是拦截器,通过拦截器,我们可以隐式地为我们的sql注入分页查询的功能,也可以将查询totalNum和totalPage的功能放入到拦截器中进行。
Mybatis拦截器的使用
首先,使用Mybatis的拦截器需要实现拦截器的接口,org.apache.ibatis.plugin.Interceptor:
1 | "prepare", args = {Connection.class, Integer.class})) ( (type = StatementHandler.class, method = |
@Interceptor注解标识这个类是一个Mybatis的拦截器,@Signature注解表明了要拦截哪个类的哪个方法(StatementHandler类的prepare方法,该方法的参数为Connection.class和Integer.class)
Interceptor接口有三个方法,分别是intercept、plugin和setProperties,其中plugin方法是用来封装目标对象的,Mybatis中已经有一个实现的方法——org.apache.ibatis.plugin.Plugin.wrap,研究源码会发现,该方法实际上就是一个动态代理的过程,将我们写在Interceptor中的intercept方法添加在拦截的方法前面。
而intercept方法则是拦截器的核心,我们通过该方法来具体实现拦截器的功能。
setProperties方法貌似是用来设置Mybatis属性的。
除此之外,还要将这个拦截器注册到SQLSessionFactory的Bean中:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis/config.xml"/>
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/>
<property name="dataSource" ref="dataSource"/>
<property name="plugins">
<array>
<ref bean="myInterceptor"/>
</array>
</property>
</bean>
<bean id="myInterceptor" class="com.netease.haitao.MyInterceptor"/>
写一个分页的拦截器
首先定义一个PagePOJO,包含以下几个属性:
/**
* 当前页,1基址
*/
private Integer currentPage = null;
/**
* 每页记录数
*/
private Integer recordPerPage = null;
/**
* 总页数
*/
private Integer totalPage = null;
/**
* 总记录数
*/
private Integer totalRecord = null;
然后开始定义自己的intercept方法(getValueByFieldName和getFieldByFieldName等是通过反射实现的一些方法):
RoutingStatementHandler statementHandler = (RoutingStatementHandler)invocation.getTarget();
BaseStatementHandler delegate = (BaseStatementHandler) getValueByFieldName(statementHandler, "delegate");
MappedStatement mappedStatement = (MappedStatement) getValueByFieldName(delegate, "mappedStatement");
BoundSql boundSql = delegate.getBoundSql();
String sql = boundSql.getSql();
if(mappedStatement.getId().matches(".*ByPage")) {
Object parameterObject = boundSql.getParameterObject();
if(parameterObject != null) {
PagePOJO pagePOJO = null;
pagePOJO = (PagePOJO)((Map<?, ?>)parameterObject).get("pagePOJO");
if (pagePOJO == null) {
// 不需要分页信息,直接返回
return invocation.proceed();
}
int count = 0;
Connection connection = (Connection) invocation.getArgs()[0];
String countSql = this.dialect.getCountString(sql); // 通过解析原sql生成计数的sql语句
try (PreparedStatement countStmt = connection.prepareStatement(countSql);ResultSet rs = countStmt.executeQuery()) {
if (rs.next()) {
count = rs.getInt(1);
}
} catch (Exception e) {
LOG.error("error:{} ", e);
}
// 将总数和页数插入PagePOJO中
pagePOJO.setTotalRecord(count);
pagePOJO.setTotalPage(((count - 1) / pagePOJO.getRecordPerPage()) + 1);
String pageSql = this.dialect.getLimitString(sql, pagePOJO.getOffset(), pagePOJO.getLimit()); //解析原sql生成插入limit分页的sql
setValueByFieldName(boundSql, "sql", pageSql); // 用新的sql覆盖原sql
}
}
return invocation.proceed(); // 继续执行
plugin方法:
public Object plugin(Object arg0) {
return Plugin.wrap(arg0, this);
}
而setProperties方法此处不需要使用,为空方法。
写好之后,只要在编写DAO的时候,将需要使用分页功能方法的名字写成以ByPage结尾,并且传入一个PagePOJO参数即可。
不过因为在拦截器的代码中,我们的POJO对象是通过(Map<?, ?>)parameterObject.get("pagePOJO")
来取得的,因此传入参数的时候,需要借助Map传入,或者使用@Param(“pagePOJO”)注解,为参数设置一个key,否则会抛异常哦