Mybatis拦截器实现分页

本文介绍使用Mybatis拦截器,实现分页;并且在dao层,直接返回自定义的分页对象。

最终dao层结果:

public interface ModelMapper {
	Page<Model> pageByConditions(RowBounds rowBounds, Model record);
}

接下来一步一步来实现分页。

一.创建Page对象:

public class Page<T> extends PageList<T>  {

	private int pageNo = 1;// 页码,默认是第一页
	private int pageSize = 15;// 每页显示的记录数,默认是15
	private int totalRecord;// 总记录数
	private int totalPage;// 总页数

	public Page() {

	}

	public Page(int pageNo, int pageSize, int totalRecord,
			List<T> results) {
		this.pageNo = pageNo;
		this.pageSize = pageSize;
		this.totalRecord = totalRecord;
		this.setResult(results);
		int totalPage = totalRecord % pageSize == 0 ? totalRecord / pageSize : totalRecord / pageSize + 1;
		this.setTotalPage(totalPage);
	}

	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;
	}

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

  可以发现,这里继承了一个PageList类;这个类也是自己创建的一个类,实现List接口。为什么要PageList这个类,是因为Page需要实现List接口,而接口中的抽象方法,需要逐一实现,所以提供PageList在统一的地方写实现List接口的方法。

  为什么Page需要实现List接口,这个会在稍后的代码中做解释。

PageList类:

public class PageList<T> implements List<T> {

	private List<T> result;

	public List<T> getResult() {
		return result;
	}

	public void setResult(List<T> result) {
		this.result = result;
	}

	@Override
	public int size() {
		return result.size();
	}

	@Override
	public boolean isEmpty() {
		return result.isEmpty();
	}

	@Override
	public boolean contains(Object o) {
		return result.contains(o);
	}

	@Override
	public Iterator<T> iterator() {
		return result.iterator();
	}

	@Override
	public Object[] toArray() {
		return result.toArray();
	}

	@Override
	public <E> E[] toArray(E[] a) {
		return result.toArray(a);
	}

	@Override
	public boolean add(T e) {
		return result.add(e);
	}

	@Override
	public boolean remove(Object o) {
		return result.remove(o);
	}

	@Override
	public boolean containsAll(Collection<?> c) {
		return result.containsAll(c);
	}

	@Override
	public boolean addAll(Collection<? extends T> c) {
		return result.addAll(c);
	}

	@Override
	public boolean addAll(int index, Collection<? extends T> c) {
		return result.addAll(index, c);
	}

	@Override
	public boolean removeAll(Collection<?> c) {
		return result.removeAll(c);
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		return result.retainAll(c);
	}

	@Override
	public void clear() {
		result.clear();
	}

	@Override
	public T get(int index) {
		return result.get(index);
	}

	@Override
	public T set(int index, T element) {
		return result.set(index, element);
	}

	@Override
	public void add(int index, T element) {
		result.add(index, element);
	}

	@Override
	public T remove(int index) {
		return result.remove(index);
	}

	@Override
	public int indexOf(Object o) {
		return result.indexOf(o);
	}

	@Override
	public int lastIndexOf(Object o) {
		return result.lastIndexOf(o);
	}

	@Override
	public ListIterator<T> listIterator() {
		return result.listIterator();
	}

	@Override
	public ListIterator<T> listIterator(int index) {
		return result.listIterator(index);
	}

	@Override
	public List<T> subList(int fromIndex, int toIndex) {
		return result.subList(fromIndex, toIndex);
	}
}

二.提供Dao以及mapper.xml

  dao的写法:

Page<Model> pageByConditions(RowBounds rowBounds, Model record);

  mapper.xml:

  <!-- 表名 -->
  <sql id="tableName" >
    model
  </sql>

  <!-- 数据表所有列名 -->
  <sql id="Base_Column_List" >
    id,
    name
  </sql>

  <!-- 查询字段 -->
  <sql id="Base_Search_Param" >
    <if test="id != null" >
          and id = #{id,jdbcType=INTEGER}
    </if>
    <if test="name != null" >
          and name = #{name,jdbcType=VARCHAR}
    </if>
  </sql>

  <!-- 分页查询语句 -->
  <select id="pageByConditions" resultMap="BaseResultMap">
      SELECT
          <include refid="Base_Column_List" />
      FROM
          <include refid="tableName" />
      WHERE 1=1
          <include refid="Base_Search_Param" />
  </select>

  ok,以上都是mybatis的基本操作,就不做多余解释。

三.创建拦截器:

  拦截器原理以及执行顺序,可参考:http://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html、http://blog.csdn.net/abcd898989/article/details/51261163

  我们需要做的是创建一个拦截器(PageInterceptor)、一个执行者(PageExecutor)。

  1.PageInteceptor:实现Inteceptor接口,将PageExecutor进行执行,拦截sql添加分页sql(limit xx,xx)

  2.PageExecutor:实现Executor接口,在查询时,添加查询总数并修改返回值类型。因为要做的是分页,是查询操作,所以里边的非查询方法都使用基本的实现,只修改两个query方法。

PageInteceptor完整代码:

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
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.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

@Intercepts({
        @Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class }),
        @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class PageInterceptor implements Interceptor {

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();

    private String pattern = "^.*page.*$";    // 需要进行分页操作的字符串正则表达式

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getTarget() instanceof StatementHandler) {
            return handleStatementHandler(invocation);
        }
        return invocation.proceed();
    }

    /**
     * @param invocation
     * @return
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    private Object handleStatementHandler(Invocation invocation)
            throws InvocationTargetException, IllegalAccessException {
        StatementHandler statementHandler = (StatementHandler) invocation
                .getTarget();
        MetaObject metaStatementHandler = MetaObject.forObject(
                statementHandler, DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY);
        RowBounds rowBounds = (RowBounds) metaStatementHandler
                .getValue("delegate.rowBounds");
        if (rowBounds == null || (rowBounds.getOffset() == RowBounds.NO_ROW_OFFSET && rowBounds
                .getLimit() == RowBounds.NO_ROW_LIMIT)) {
            return invocation.proceed();
        }

        // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环可以分离出最原始的的目标类)
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = MetaObject.forObject(object,
                    DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        }
        // 分离最后一个代理对象的目标类
        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = MetaObject.forObject(object,
                    DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
        }

        // 将mybatis的内存分页,调整为物理分页
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        String sql = boundSql.getSql();
        // 重写sql
        String pageSql = sql + " LIMIT " + rowBounds.getOffset() + "," + rowBounds.getLimit();
        metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
        // 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数
        metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
        metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);

        // 将执行权交给下一个拦截器
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        if (Executor.class.isAssignableFrom(o.getClass())) {
            PageExecutor executor = new PageExecutor((Executor)o, pattern);
            return Plugin.wrap(executor, this);
        } else if (o instanceof StatementHandler) {
            return Plugin.wrap(o, this);
        }
        return o;
    }

    @Override
    public void setProperties(Properties properties) {
    }

}

PageExecutor完整代码:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

public class PageExecutor implements Executor {

    private Executor executor;

    private String pattern;

    public PageExecutor(Executor executor, String pattern) {
        this.executor = executor;
        this.pattern = pattern;
    }

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        return executor.update(ms, parameter);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
            CacheKey cacheKey, BoundSql boundSql) throws SQLException {
        RowBounds rb = new RowBounds(rowBounds.getOffset(), rowBounds.getLimit());
        List<E> rows = executor.query(ms, parameter, rowBounds, resultHandler,
                cacheKey, boundSql);
        return pageResolver(rows, ms, parameter, rb);
    }

    /**
     * 修改返回值类型
     * @param rows
     * @param ms
     * @param parameter
     * @param rowBounds
     * @return
     */
    private <E> List<E> pageResolver(List<E> rows, MappedStatement ms,
            Object parameter, RowBounds rowBounds) {
        String msid = ms.getId();
        // 如果需要分页查询,修改返回类型为Page对象
        if (msid.matches(pattern)) {
            int count = getCount(ms, parameter);
            int offset = rowBounds.getOffset();
            int pagesize = rowBounds.getLimit();
            return new Page<E>(offset/pagesize + 1, pagesize, count, rows);
        }
        return rows;
    }

    /**
     * 获取总数
     * @param ms
     * @param parameter
     * @return
     */
    private int getCount(MappedStatement ms, Object parameter) {
        BoundSql bsql = ms.getBoundSql(parameter);
        String sql = bsql.getSql();
        String countSql = getCountSql(sql);
        Connection connection = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            connection = ms.getConfiguration().getEnvironment().getDataSource()
                    .getConnection();
            stmt = connection.prepareStatement(countSql);
            rs = stmt.executeQuery();
            if (rs.next())
                return rs.getInt(1);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (connection != null && !connection.isClosed()) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return 0;
    }

    private String getCountSql(String sql) {
        String countHql = " SELECT count(*) "
                + removeSelect(removeOrders(sql));

        return countHql;
    }

    protected String removeOrders(String sql) {
        Pattern p = Pattern.compile("ORDER\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(sql);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            m.appendReplacement(sb, "");
        }
        m.appendTail(sb);
        return sb.toString();
    }

    // 去除sql语句中select子句
    private static String removeSelect(String hql) {
        int beginPos = hql.toLowerCase().indexOf("from");
        if (beginPos < 0) {
            throw new IllegalArgumentException(" hql : " + hql + " must has a keyword ‘from‘");
        }
        return hql.substring(beginPos);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
            throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        return query(ms, parameter, rowBounds, resultHandler,
                executor.createCacheKey(ms, parameter, rowBounds, boundSql),
                boundSql);
    }

    @Override
    public List<BatchResult> flushStatements() throws SQLException {
        return executor.flushStatements();
    }

    @Override
    public void commit(boolean required) throws SQLException {
        executor.commit(required);
    }

    @Override
    public void rollback(boolean required) throws SQLException {
        executor.rollback(required);
    }

    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject,
            RowBounds rowBounds, BoundSql boundSql) {
        return executor
                .createCacheKey(ms, parameterObject, rowBounds, boundSql);
    }

    @Override
    public boolean isCached(MappedStatement ms, CacheKey key) {
        return executor.isCached(ms, key);
    }

    @Override
    public void clearLocalCache() {
        executor.clearLocalCache();
    }

    @Override
    public void deferLoad(MappedStatement ms, MetaObject resultObject,
            String property, CacheKey key, Class<?> targetType) {
        executor.deferLoad(ms, resultObject, property, key, targetType);
    }

    @Override
    public Transaction getTransaction() {
        return executor.getTransaction();
    }

    @Override
    public void close(boolean forceRollback) {
        executor.close(forceRollback);
    }

    @Override
    public boolean isClosed() {
        return executor.isClosed();
    }

}

  关于Page需要实现List接口的原因:可以看到,query方法返回值是List<E>,而我们现在要在dao中使用Page<E>对象来接收mybatis返回的结果,所以需要让Page实现List接口。

  分页查询执行顺序:进入PageInterceptor的plugin方法,拦截到执行者,进入PageExecutor的query方法,执行executor.query()时,又再次回到PageInterceptor的plugin方法,这次会执行

  进入intercept方法,将执行的sql拼接上分页限制语句,然后查询出数据结果集合。executor.query()执行完成后,继续执行pageResolver,如果方法名称和配置的需要执行分页操作的字符串匹配时,查询数据总量,并返回Page对象;如果不匹配,直接返回List对象。

四.xml配置:

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:/conf/mybatis/mybaties-config.xml"></property>
        <property name="mapperLocations">
            <list>
                <value>classpath:/conf/mybatis/**/*-mapper.xml</value>
            </list>
        </property>
        <property name="plugins">
            <list>
                <ref bean="pageInterceptor"/>
            </list>
        </property>
    </bean>

     <bean id="pageInterceptor" class="cn.com.common.PageInterceptor">
        <property name="pattern" value="^.*page.*$"></property>
    </bean>

五.测试代码:

    @Test
    public void testPage() {
        int pageNo = 1;
        int pageSize = 10;
        RowBounds bounds = new RowBounds((pageNo - 1) * pageSize, pageSize);
        Model record = new Model();

        Page<Model> list = modelMapper.pageByConditions(bounds, record);
    }
时间: 2024-09-30 05:22:30

Mybatis拦截器实现分页的相关文章

mybatis拦截器实现分页功能的示例讲解

import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Properties; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apach

Mybatis拦截器介绍及分页插件

1.1    目录 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 1.2     前言 拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法.Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑.打个比方,对于Executor

Mybatis拦截器介绍

拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法.Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑.打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor.ReuseExecutor.SimpleExecutor和CachingExecutor.这个时候如果你觉得这几种实现对于Execu

【Mybatis】1、Mybatis拦截器

MyBatis拦截器原理探究 http://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html MyBatis 拦截器 (实现分页功能) http://www.cnblogs.com/jethypc/p/5149183.html 由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理 http://zhangbo-peipei-163-com.iteye.com/blog/2033832 使用方法 https://github.

【Mybatis】1、Mybatis拦截器学习资料汇总

MyBatis拦截器原理探究 http://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html [myBatis]Mybatis中的拦截器 http://blog.csdn.net/moshenglv/article/details/52699976 MyBatis 拦截器 (实现分页功能) http://www.cnblogs.com/jethypc/p/5149183.html 由浅入深分析mybatis通过动态代理实现拦截器(插件

Mybatis 拦截器(二)

Mybatis拦截器介绍 拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法.Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑.打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor.ReuseExecutor.SimpleExecutor和CachingExecutor.这个时候如果你觉

mybatis拦截器使用

[toc] mybatis拦截器入门 mybatis 拦截器接口Interceptor Interceptor接口,我们通过这个接口可以自定义我们自己的拦截器,从而实现在拦截的时候写上我们自己的一些逻辑.该接口提供了三个方法, 接口介绍: intercept方法: intercept方法是在进行拦截的时候要执行的方法. plugin方法: 该方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理. setProperties方法: mybatis配置文件中co

MyBatis拦截器原理探究

前言: MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClose

Mybatis拦截器源码深度解析

目录: 一. 建立拦截器链1. 创建对象2. 建立配置文件3. 加载拦截器链二. 方法调用解析1. 对请求对象进行拦截器包装2. 执行调用三. 小结 Mybatis拦截器 可以帮助我们在执行sql语句过程中增加插件以实现一些通用的逻辑,比如对查询sql分页.数据权限处理等. 允许使用插件拦截的方法调用包括: - Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)