MyBatis拦截器分页

本文介绍了如何使用MyBatis的Pagination Interceptor实现分页功能。步骤包括引入拦截器,配置sqlMapConfig.xml, mapper文件中添加分页方法,以及在dao、service和controller层的实现。需要注意在调用时从params.fullName中获取分页参数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用Mybatis拦截器实现分页,类似aop的效果.拦截sql语句,修改为分页查询语句.

1、引入pagination inteceptor拦截器和分页page
2、在sqlMapConfig.xml中配置拦截器插件

<!-- 分页拦截器,拦截用户提交的查询,只查询当前页数据;提高响应速度。 -->
<plugins>  
        <plugin  interceptor="cn.itcast.jk.pagination.PageInterceptor">  
            <property name="databaseType" value="oracle"/>  
        </plugin>  
    </plugins>  

3、在mapper文件中添加分页方法

注意参数:params.fullName,参数封装的Page.params Map集合中,所以调用时要到其中获取。

<!-- 带条件分页查询 -->
    <select id="findPage" parameterType="cn.itcast.jk.pagination.Page" resultMap="factoryRM">  
        select * from FACTORY_C
        where 1=1
        <if test="params.fullName != null">and FULL_NAME like #{params.fullName}</if>
        <if test="params.state != null">and STATE = #{params.state}</if>
        order by ORDER_NO
    </select> 

4、实现dao、service
5、controller中调用

//查询
    @RequestMapping("/basicinfo/factory/list.action")
    public String list(String fullName, Model model, Page<Factory> page){

        //设置查询条件
        Map<String, Object> params = new HashMap<String, Object>();
        if(fullName!=null&&!fullName.equals("")){
            params.put("fullName", "%" + fullName + "%");
        }
        params.put("state", 1);
        page.setParams(params);

        List<Factory> dataList = factoryService.findPage(page);

        model.addAttribute("dataList", dataList);
        model.addAttribute("pageLinks", page.pageLinks("list.action"));
        return "/basicinfo/factory/jFactoryList.jsp";
    }
Page.java:
package cn.itcast.jk.pagination;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 对分页的基本数据进行一个简单的封装
 */
public class Page<T> {
    private int pageNo = 1;         //页码,默认是第一页
    private int pageSize = 3;       //每页显示的记录数,默认是10
    private int totalRecord;        //总记录数
    private int totalPage;          //总页数
    private List<T> results;        //对应的当前页记录
    private Map<String, Object> params = new HashMap<String, Object>();     //其他的参数我们把它分装成一个Map对象

    public int getPageNo() {
       return pageNo;
    }

    public void setPageNo(int pageNo) {
       this.pageNo = pageNo;
    }

    public int getPageSize() {
       return pageSize;
    }

    public void setPageSize(int pageSize) {
       this.pageSize = pageSize;
    }

    public int getTotalRecord() {
       return totalRecord;
    }

    public void setTotalRecord(int totalRecord) {
       this.totalRecord = totalRecord;
       //在设置总页数的时候计算出对应的总页数,在下面的三目运算中加法拥有更高的优先级,所以最后可以不加括号。
       int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;
       this.setTotalPage(totalPage);
    }

    public int getTotalPage() {
       return totalPage;
    }

    public void setTotalPage(int totalPage) {
       this.totalPage = totalPage;
    }

    public List<T> getResults() {
       return results;
    }

    public void setResults(List<T> results) {
       this.results = results;
    }

    public Map<String, Object> getParams() {
       return params;
    }

    public void setParams(Map<String, Object> params) {
       this.params = params;
    }

    public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append("Page [pageNo=").append(pageNo).append(", pageSize=").append(pageSize).append(", results=").append(results).append(", totalPage=").append(totalPage).append(", totalRecord=").append(totalRecord).append("]");
       return builder.toString();
    }

    /* 页面链接 */
    public String pageLinks(String url) {
        int endPage = this.totalRecord/pageSize +1;

        StringBuffer sBuf = new StringBuffer();

        sBuf.append("<input type=\"hidden\" name=\"pageNo\" value=\"").append(this.pageNo).append("\">");       //分页参数:当前页隐藏域

        sBuf.append("<span class=\"noprint\" style=\"padding:5px;\">");

        sBuf.append("&nbsp;第").append(this.pageNo).append("页 / 共").append(endPage).append("页&nbsp;");
        sBuf.append("&nbsp;总共").append(this.totalRecord).append("条记录 每页").append(this.pageSize).append("条记录&nbsp;");

        sBuf.append("<a href=\"").append(url).append("?pageNo=1");
        sBuf.append("\">[首页]");
        sBuf.append("</a>&nbsp;");

        sBuf.append("<a href=\"").append(url).append("?pageNo=");
        if(pageNo<=1){
            sBuf.append(1);
        }else{
            sBuf.append(pageNo-1);
        }   
        sBuf.append("\">[上一页]");
        sBuf.append("</a>&nbsp;");


        sBuf.append("<a href=\"").append(url).append("?pageNo=");
        if(pageNo>=endPage){
            sBuf.append(endPage);
        }else{
            sBuf.append(pageNo+1);
        }   
        sBuf.append("\">[下一页]");
        sBuf.append("</a>&nbsp;");

        sBuf.append("<a href=\"").append(url).append("?pageNo=").append(endPage);
        sBuf.append("\">[末页]");
        sBuf.append("</a>&nbsp;");

        sBuf.append("</span>");

        return sBuf.toString();
    }

}
PageInterceptor.java:
package cn.itcast.jk.pagination;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
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.scripting.defaults.DefaultParameterHandler;

/**
 * 
 * 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。 利用拦截器实现Mybatis分页的原理:
 * 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象
 * ,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的Sql语句
 * 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。
 * 在Mybatis中Statement语句是通过RoutingStatementHandler对象的prepare方法生成的。
 * 
 * 所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,
 * 然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句, 之后再调用
 * StatementHandler对象的prepare方法,即调用invocation.proceed()。
 * 
 * 对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少,
 * 这是通过获取到了原始的Sql语句后,把它改为对应的统计语句,再利用Mybatis封装好的参数和设置参数的功能把Sql语句中的参数进行替换,
 * 之后再执行查询记录数的Sql语句进行总记录数的统计。
 * 
 */
@Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class PageInterceptor implements Interceptor {
    private String databaseType; // 数据库类型,不同的数据库有不同的分页方法

    // 拦截后要执行的方法
    public Object intercept(Invocation invocation) throws Throwable {
        // 对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另一个是抽象类BaseStatementHandler,
        // BaseStatementHandler有三个子类,分别是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,
        // SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler是处理PreparedStatement的,而CallableStatementHandler是处理CallableStatement的。
        // Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个StatementHandler类型的delegate属性,
        // RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、
        // PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。

        // 我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法,
        // 又因为Mybatis只有在建立RoutingStatementHandler的时候是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。

        RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
        // 通过反射获取到当前RoutingStatementHandler对象的delegate属性
        StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
        // 获取到当前StatementHandler的
        // boundSql,这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的,因为之前已经说过了
        // RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。
        BoundSql boundSql = delegate.getBoundSql();
        // 拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数对象
        Object obj = boundSql.getParameterObject();
        // 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
//      if(obj instanceof Map){
//          Map map = (Map)obj;
//          Page<?> page = (Page<?>) map.get("page");
//      }
        if (obj instanceof Page<?>) {               //拦截参数为Page对象的,其他放过
            Page<?> page = (Page<?>) obj;
            // 通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
            MappedStatement mappedStatement = (MappedStatement) ReflectUtil
                    .getFieldValue(delegate, "mappedStatement");
            // 拦截到的prepare方法参数是一个Connection对象
            Connection connection = (Connection) invocation.getArgs()[0];
            // 获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
            String sql = boundSql.getSql();
            // 给当前的page参数对象设置总记录数
            this.setTotalRecord(page, mappedStatement, connection);
            // 获取分页Sql语句
            String pageSql = this.getPageSql(page, sql);
            // 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
            ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
        }
        return invocation.proceed();
    }

    /**
     * 拦截器对应的封装原始对象的方法
     */
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 设置注册拦截器时设定的属性
     */
    public void setProperties(Properties properties) {
        this.databaseType = properties.getProperty("databaseType");
    }

    /**
     * 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle 其它的数据库都 没有进行分页
     * 
     * @param page 分页对象
     * @param sql 原sql语句
     * @return
     */
    private String getPageSql(Page<?> page, String sql) {
        StringBuffer sqlBuffer = new StringBuffer(sql);
        if ("mysql".equalsIgnoreCase(databaseType)) {
            return getMysqlPageSql(page, sqlBuffer);
        } else if ("oracle".equalsIgnoreCase(databaseType)) {
            return getOraclePageSql(page, sqlBuffer);
        }
        return sqlBuffer.toString();
    }

    /**
     * 获取Mysql数据库的分页查询语句
     * 
     * @param page
     *            分页对象
     * @param sqlBuffer
     *            包含原sql语句的StringBuffer对象
     * @return Mysql数据库分页语句
     */
    private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {
        // 计算第一条记录的位置,Mysql中记录的位置是从0开始的。
        int offset = (page.getPageNo() - 1) * page.getPageSize();
        sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
        return sqlBuffer.toString();
    }

    /**
     * 获取Oracle数据库的分页查询语句
     * 
     * @param page
     *            分页对象
     * @param sqlBuffer
     *            包含原sql语句的StringBuffer对象
     * @return Oracle数据库的分页查询语句
     */
    private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {
        // 计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的
        int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
        sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());
        sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
        // 上面的Sql语句拼接之后大概是这个样子:
        // select * from (select u.*, rownum r from (select * from t_user) u
        // where rownum < 31) where r >= 10
        return sqlBuffer.toString();
    }

    /**
     * 给当前的参数对象page设置总记录数
     * 
     * @param page Mapper映射语句对应的参数对象
     * @param mappedStatement Mapper映射语句
     * @param connection 当前的数据库连接
     */
    private void setTotalRecord(Page<?> page, MappedStatement mappedStatement,
            Connection connection) {
        // 获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
        // delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
        BoundSql boundSql = mappedStatement.getBoundSql(page);
        // 获取到我们自己写在Mapper映射语句中对应的Sql语句
        String sql = boundSql.getSql();
        // 通过查询Sql语句获取到对应的计算总记录数的sql语句
        String countSql = this.getCountSql(sql);
        // 通过BoundSql获取对应的参数映射
        List<ParameterMapping> parameterMappings = boundSql .getParameterMappings();
        // 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
        BoundSql countBoundSql = new BoundSql(
                mappedStatement.getConfiguration(), countSql,
                parameterMappings, page);
        // 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
        ParameterHandler parameterHandler = new DefaultParameterHandler(
                mappedStatement, page, countBoundSql);
        // 通过connection建立一个countSql对应的PreparedStatement对象。
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            pstmt = connection.prepareStatement(countSql);
            // 通过parameterHandler给PreparedStatement对象设置参数
            parameterHandler.setParameters(pstmt);
            // 之后就是执行获取总记录数的Sql语句和获取结果了。
            rs = pstmt.executeQuery();
            if (rs.next()) {
                int totalRecord = rs.getInt(1);
                // 给当前的参数page对象设置总记录数
                page.setTotalRecord(totalRecord);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (rs != null)
                    rs.close();
                if (pstmt != null)
                    pstmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 根据原Sql语句获取对应的查询总记录数的Sql语句
     * 
     * @param sql
     * @return
     */
    private String getCountSql(String sql) {
        int index = sql.indexOf("from");
        return "select count(*) " + sql.substring(index);
    }

    /**
     * 利用反射进行操作的一个工具类
     * 
     */
    private static class ReflectUtil {
        /**
         * 利用反射获取指定对象的指定属性
         * 
         * @param obj
         *            目标对象
         * @param fieldName
         *            目标属性
         * @return 目标属性的值
         */
        public static Object getFieldValue(Object obj, String fieldName) {
            Object result = null;
            Field field = ReflectUtil.getField(obj, fieldName);
            if (field != null) {
                field.setAccessible(true);
                try {
                    result = field.get(obj);
                } catch (IllegalArgumentException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            return result;
        }

        /**
         * 利用反射获取指定对象里面的指定属性
         * 
         * @param obj
         *            目标对象
         * @param fieldName
         *            目标属性
         * @return 目标字段
         */
        private static Field getField(Object obj, String fieldName) {
            Field field = null;
            for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz
                    .getSuperclass()) {
                try {
                    field = clazz.getDeclaredField(fieldName);
                    break;
                } catch (NoSuchFieldException e) {
                    // 这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。
                }
            }
            return field;
        }

        /**
         * 利用反射设置指定对象的指定属性为指定的值
         * 
         * @param obj
         *            目标对象
         * @param fieldName
         *            目标属性
         * @param fieldValue
         *            目标值
         */
        public static void setFieldValue(Object obj, String fieldName,
                String fieldValue) {
            Field field = ReflectUtil.getField(obj, fieldName);
            if (field != null) {
                try {
                    field.setAccessible(true);
                    field.set(obj, fieldValue);
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
Mybatis拦截器分页是指通过使用Mybatis拦截器机制来实现数据库查询结果的分页显示。拦截器的作用是在方法执行前后加入自定义的逻辑代码,从而实现对方法的拦截和处理。在Mybatis中,我们可以使用拦截器来拦截Executor接口中的query方法,该方法用于执行数据库查询操作。通过拦截query方法,我们可以在查询前后进行一些处理,比如对查询结果进行分页处理。 在实现Mybatis拦截器分页的过程中,我们需要定义一个实现了Interceptor接口的拦截器类,并在该类上使用@Intercepts注解来定义拦截点。在拦截器类中,我们可以通过重写intercept方法来实现对query方法的拦截和处理。在intercept方法中,我们可以获取到方法的参数和执行结果,并根据需要进行分页处理。 为了在Mybatis中使用拦截器分页,我们还需要在Mybatis的配置文件中注册拦截器。在配置文件中,我们可以使用<plugins>标签来注册拦截器,指定拦截器的类名。通过注册拦截器,我们可以使拦截器生效,并在查询操作中进行分页处理。 需要注意的是,不同的数据库可能有不同的分页语法,为了保证程序的兼容性,最好将数据库方言的设置抽出来。可以定义一个数据库方言配置接口,通过实现该接口来设置不同数据库的分页语法。在分页处理中,我们可以根据数据库方言来生成相应的分页SQL语句,从而实现对不同数据库的兼容性支持。 #### 引用[.reference_title] - *1* *2* *3* [MyBatis拦截器实现分页](https://blog.youkuaiyun.com/wqh8522/article/details/78972135)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值