Mybatis分页拦截器


背景

在平时的后端开发工作中,经常会使用到分页查询的功能,往往这时候就要把pageNum和pageSize这样的参数传入到dao中,而传回给前端的数据中还会包括有totalNum和totalPage这样的值,就得分开几条sql去查询。

有没有好的方法可以自动进行这些繁琐而多余的操作呢?mybatis为我们提供了插件这样的方式,插件实际上也就是拦截器,通过拦截器,我们可以隐式地为我们的sql注入分页查询的功能,也可以将查询totalNum和totalPage的功能放入到拦截器中进行。

Mybatis拦截器的使用

首先,使用Mybatis的拦截器需要实现拦截器的接口,org.apache.ibatis.plugin.Interceptor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
public class MybatisInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
return null;
}

@Override
public Object plugin(Object target) {
return null;
}

@Override
public void setProperties(Properties properties) {

}
}

@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,否则会抛异常哦

-------------本文结束感谢您的阅读-------------
0%