MyBatis 物理分页
MyBatis使用RowBounds实现的分页是逻辑分页,也就是先把数据记录全部查询出来,然在再根据offset和limit截断记录返回
为了在数据库层面上实现物理分页,又不改变原来MyBatis的函数逻辑,可以编写plugin截获MyBatis Executor的statementhandler,重写SQL来执行查询
参考资料: http://blog.csdn.net/hupanfeng/article/details/9265341
下面的插件代码只针对MySQL
plugin代码
package plugin; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties; import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
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.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds; /**
* 通过拦截<code>StatementHandler</code>的<code>prepare</code>方法,重写sql语句实现物理分页。
* 老规矩,签名里要拦截的类型只能是接口。
*
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class PaginationInterceptor implements Interceptor {
private static final Log logger = LogFactory.getLog(PaginationInterceptor.class);
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
private static String DEFAULT_PAGE_SQL_ID = ".*Page$"; // 需要拦截的ID(正则匹配) @Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
DEFAULT_OBJECT_WRAPPER_FACTORY);
RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
// 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环可以分离出最原始的的目标类)
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);
} // property在mybatis settings文件内配置
Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration"); // 设置pageSqlId
String pageSqlId = configuration.getVariables().getProperty("pageSqlId");
if (null == pageSqlId || "".equals(pageSqlId)) {
logger.warn("Property pageSqlId is not setted,use default '.*Page$' ");
pageSqlId = DEFAULT_PAGE_SQL_ID;
} MappedStatement mappedStatement = (MappedStatement)
metaStatementHandler.getValue("delegate.mappedStatement");
// 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的MappedStatement的sql
if (mappedStatement.getId().matches(pageSqlId)) {
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
Object parameterObject = boundSql.getParameterObject();
if (parameterObject == null) {
throw new NullPointerException("parameterObject is null!");
} else {
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 target) {
// 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
} @Override
public void setProperties(Properties properties) {
//To change body of implemented methods use File | Settings | File Templates.
} }
配置plugin
<plugins>
<plugin interceptor="plugin.PaginationInterceptor" />
</plugins>
查询SQL
<!-- 测试分页查询 -->
<select id="selectUserByPage" resultMap="dao.base.userResultMap">
<![CDATA[
SELECT * FROM user
]]>
</select>
调用示例
@Override
public List<User> selectUserByPage(int offset, int limit) {
RowBounds rowBounds = new RowBounds(offset, limit);
return getSqlSession().selectList("dao.userdao.selectUserByPage", new Object(), rowBounds);
}
另外,结合Spring MVC,编写翻页和生成页码代码
页码类
package util; /**
* Created with IntelliJ IDEA.
* User: zhenwei.liu
* Date: 13-8-7
* Time: 上午10:29
* To change this template use File | Settings | File Templates.
*/
public class Pagination {
private String url; // 页码url
private int pageSize = 10; // 每页显示记录数
private int currentPage = 1; // 当前页码
private int maxPage = Integer.MAX_VALUE; // 最大页数 // 获取offset
public int getOffset() {
return (currentPage - 1) * pageSize;
} // 获取limit
public int getLimit() {
return getPageSize();
} public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
} public int getPageSize() {
return pageSize;
} public void setPageSize(int pageSize) {
this.pageSize = pageSize;
} public int getCurrentPage() {
return currentPage;
} public void setCurrentPage(int currentPage) {
if (currentPage < 1)
currentPage = 1;
if (currentPage > maxPage)
currentPage = maxPage;
this.currentPage = currentPage;
} public int getMaxPage() {
return maxPage;
} public void setMaxPage(int maxPage) {
this.maxPage = maxPage;
}
}
为了计算最大页码,需要知道数据表的总记录数,查询SQL如下
<!-- 记录总数 -->
<select id="countUser" resultType="Integer">
<![CDATA[
SELECT COUNT(*) FROM user
]]>
</select>
@Override
public Integer countTable() {
return getSqlSession().selectOne("dao.userdao.countUser");
}
Controller中的使用
@RequestMapping("/getUserByPage")
public String getUserByPage(@RequestParam
int page, Model model) {
pagination.setCurrentPage(page);
pagination.setUrl(getCurrentUrl());
pagination.setMaxPage(userDao.countTable() / pagination.getPageSize() + 1);
List<User> userList = userDao.selectUserByPage(
pagination.getOffset(), pagination.getLimit());
model.addAttribute(pagination);
model.addAttribute(userList);
return "index";
}
MyBatis 物理分页的更多相关文章
- 2017.12.25 Mybatis物理分页插件PageHelper的使用(二)
参考来自: 官方文档的说明:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md 上篇博客 ...
- MyBatis物理分页的代码实现
一.分页 MyBatis有两种分页方法:内存分页,也就是假分页,本质是查出所有的数据然后根据游标的方式,截取需要的记录,如果数据量大,执行效率低,可能造成内存溢出.物理分页,就是数据库本身提供了分页方 ...
- 2017.12.14 Mybatis物理分页插件PageHelper的使用(一)
参考来自: http://www.360doc.com/content/15/0728/15/12642656_487954693.shtml https://www.cnblogs.com/digd ...
- 关于struts2的过滤器和mybatis的插件的分析
网上一搜,发现一篇写的非常棒的博文,就直接复制过来了,供以后复习使用. 前辈博文链接:共三篇: http://jimgreat.iteye.com/blog/1616671: http://jimgr ...
- 【转】mybatis实战教程(mybatis in action),mybatis入门到精通
MyBatis 目录(?)[-] mybatis实战教程mybatis in action之一开发环境搭建 mybatis实战教程mybatis in action之二以接口的方式编程 mybatis ...
- mybatis实战教程(mybatis in action)之七:实现mybatis分页
上一篇文章里已经讲到了mybatis与spring MVC的集成,并且做了一个列表展示,显示出所有article 列表,但没有用到分页,在实际的项目中,分页是肯定需要的.而且是物理分页,不是内存分页. ...
- mybatis实战教程(mybatis in action),mybatis入门到精通
转自:http://www.yihaomen.com/article/java/302.htm (读者注:其实这个应该叫做很基础的入门一下下,如果你看过hibernate了那这个就非常的简单) (再加 ...
- mybatis实战教程(mybatis in action),mybatis入门到精通(转)
转自:http://www.yihaomen.com/article/java/302.htm (读者注:其实这个应该叫做很基础的入门一下下,如果你看过Hibernate了那这个就非常的简单) (再加 ...
- 【Todo】Mybatis学习-偏理论
之前写过好几篇Mybatis相关的文章: http://www.cnblogs.com/charlesblc/p/5906431.html <SSM(SpringMVC+Spring+Myba ...
随机推荐
- C Primer Plus之高级数据表示
抽象数据类型(ADT) 类型是由什么组成?一个类型(type)指定两类信息:一个属性集和一个操作集. 所以您想定义一个新的数据类型.首先,您需要提供存储数据的方式,可能是通过设计一个结构.第二 ...
- cojs 简单的求和问题 解题报告
一个上午写了两个数据生成器,三个暴力和两个正解以及一个未竣工的伪正解思路 真是累死本宝宝了 首先这个题目暴力我的数据是有很多良心分的 但是不同的暴力拿到的分数也会有所差距,由于是题解就不说暴力怎么写了 ...
- lintcode 中等题:subsets II 带重复元素的子集
题目 带重复元素的子集 给定一个可能具有重复数字的列表,返回其所有可能的子集 样例 如果 S = [1,2,2],一个可能的答案为: [ [2], [1], [1,2,2], [2,2], [1,2] ...
- lintcode :搜索二维矩阵
题目: 搜索二维矩阵 写出一个高效的算法来搜索 m × n矩阵中的值. 这个矩阵具有以下特性: 每行中的整数从左到右是排序的. 每行的第一个数大于上一行的最后一个整数. 样例 考虑下列矩阵: [ [1 ...
- C++ Primer笔记整理
1. 迭代器:迭代器是一种对象,它可以看做是游标,用来遍历标准模板库中的部分或者全部元素. 每个迭代器指向容器中确定的地址,此外,迭代器还提供一些基本操作符:*.++.==.!=.=. 2. 模板:是 ...
- sql语句面试总结
1.用一条SQL语句 查询出每门课都大于80分的学生姓名 name kecheng fenshu 张三 语文 81张三 数学 75李四 语文 ...
- QT里使用sqlite的问题,好多坑
1. 我使用sqlite,开发机上好好的,测试机上却不行.后来发现是缺少驱动(Driver not loaded Driver not loaded),代码检查了又检查,发现应该是缺少dll文件(系统 ...
- Spring使用外部的配置文件
在使用Spring做web项目的时候,通常会使用到数据库的连接信息 jdbcUrl driverClass username password 那么应该如何使用这些属性呢? 如在Spring中使用数据 ...
- Android scrollview嵌套listview运行后最先显示出来的位置不在顶部而是中间问题
scrollview里面嵌套了一个listview ,通过设置一个方法设置了listview的高度 现在的情况就是进到这个界面的时候看到的不是最上面 而是中间 ,该问题的解决办法为: mScrollV ...
- OpenCV源码阅读(1)---matx.h---mat类与vec类
matx.h matx类是opencv中的一个基础类,其位于core模块中,所执行的操作时opencv矩阵和向量的运算.如果熟悉基于matlab的图像处理,那么很容易想到,所有对图像的操作归根结底都是 ...