MyBatis物理分页的代码实现
一.分页
MyBatis有两种分页方法:内存分页,也就是假分页,本质是查出所有的数据然后根据游标的方式,截取需要的记录,如果数据量大,执行效率低,可能造成内存溢出。物理分页,就是数据库本身提供了分页方式,如MySql的limit,执行效率高,不同数据库实现不同。
MyBatis Generator使用:MyBatis Generator使用示例
Spring集成MyBatis:Spring集成MyBatis持久层框架
二.MyBatis执行流程
MyBatis执行sql流程如下图,实现数据库的物理分页,需要通过拦截StatementHandler重写的sql语句。

三.分页实现
1.实现MyBatis的Interceptor接口,创建PageInterceptor类
@Intercepts({@Signature(type=StatementHandler.class, method = "prepare", args={Connection.class, Integer.class})})
public class PageInterceptor implements Interceptor {
private String sqlRegEx = ".*Page";
public Object intercept(Invocation invocation) throws Throwable {
RoutingStatementHandler handler = (RoutingStatementHandler)invocation.getTarget();
StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
BoundSql boundSql = delegate.getBoundSql();
MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");
// 获取参数
Object parameterObject = boundSql.getParameterObject();
// 判断是否分页
if (mappedStatement.getId().matches(sqlRegEx)) {
Page page = (Page) ((Map<?, ?>) parameterObject).get("page");
if (page != null) {
Connection connection = (Connection) invocation.getArgs()[0];
// 获取mapper映射文件中对应的sql语句
String sql = boundSql.getSql();
// 给当前page参数设置总记录数
this.setPageParameter(mappedStatement, connection, boundSql, page);
// 获取分页sql语句
String pageSql = this.getPageSql(page, sql);
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
}
return invocation.proceed();
}
/**
* 从数据库里查询总的记录数并计算总页数,回写进分页参数page
* @param mappedStatement
* @param connection
* @param boundSql
* @param page
*/
private void setPageParameter(MappedStatement mappedStatement, Connection connection, BoundSql boundSql, Page page) {
// 获取mapper映射文件中对应的sql语句
String sql = boundSql.getSql();
// 获取计算总记录数的sql语句
String countSql = this.getCountSql(sql);
// 获取BoundSql参数映射
List<ParameterMapping> parameterMappinglist = boundSql.getParameterMappings();
// 构造查询总量的BoundSql
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappinglist, boundSql.getParameterObject());
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql);
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 通过connection建立countSql对应的PreparedStatement对象
pstmt = connection.prepareStatement(countSql);
parameterHandler.setParameters(pstmt);
// 执行countSql语句
rs = pstmt.executeQuery();
if (rs.next()) {
int totalRecord = rs.getInt(1);
page.setTotalRecord(totalRecord);
page.setTotalPage(totalRecord/page.getPageSize() + (totalRecord % page.getPageSize() == 0? 0: 1));
}
} 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);
}
/**
* 获取MySql数据库的分页查询语句
* @param page
* @param sql
* @return
*/
private String getPageSql(Page<?> page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql);
int offset = (page.getPageNum() - 1) * page.getPageSize();
sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
return sqlBuffer.toString();
}
/**
* 只处理StatementHandler类型
* @param o
* @return
*/
public Object plugin(Object o) {
if (o instanceof StatementHandler) {
return Plugin.wrap(o, this);
} else {
return o;
}
}
/**
* 拦截器属性设定
* @param properties
*/
public void setProperties(Properties properties) {
}
public String getSqlRegEx() {
return sqlRegEx;
}
public void setSqlRegEx(String sqlRegEx) {
this.sqlRegEx = sqlRegEx;
}
}
2.保存页面的相关信息,创建Page类
public class Page<T> {
private int pageNum = 1;
private int pageSize = 5;
private int totalRecord;
private int totalPage;
private List<T> results;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
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;
}
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;
}
}
3.通过反射获取对象的属性,创建ReflectUtil工具类,用于获取RoutingStatementHandler对象的私有属性delegate
public class ReflectUtil {
public static Object getFieldValue(Object obj, String fieldName) {
Object result = null;
Field field = ReflectUtil.getField(obj, fieldName);
if (null != field) {
field.setAccessible(true);
try {
result = field.get(obj);
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
}
return result;
}
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) {
}
}
return field;
}
public static void setFieldValue(Object obj, String fieldName, String fieldValue) {
Field field = ReflectUtil.getField(obj, fieldName);
if (null != field) {
try {
field.setAccessible(true);
field.set(obj, fieldValue);
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
}
}
}
4.启用分页Interceptor,编辑applicationContext_database.xml
<!-- mybatis配置,mapper.xml文件扫描 -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:/mybatis/mybatis.xml"/>
<property name="mapperLocations" value="classpath:/mybatis/mapper/*Mapper.xml"/>
<property name="dataSource" ref="dataSource"/> <property name="plugins">
<array>
<!-- 分页 -->
<bean class="com.learn.spring.server.intercept.PageInterceptor">
<property name="properties">
<value>
sqlRegEx = ".*Page"
</value>
</property>
</bean>
</array>
</property>
</bean>
四.调用示例
1.编辑UserServiceImpl类
@Service
public class UserServiceImpl implements UserService { @Resource
private UserDOMapper userDao; @Override
public Page<UserDO> listByCondPage(Integer status, Page page) {
Map<String, Object> param = new HashMap<>();
param.put("status", status);
param.put("page", page);
List<UserDO> userDOList = userDao.selectByCondPage(param);
page.setResults(userDOList);
return page;
}
}
2.编辑IndexController类,调用Service
@Controller
@RequestMapping("/server")
public class IndexController { @Resource
private UserService userService; @ResponseBody
@RequestMapping("/list")
public Object list(Integer status, Integer pageNum, Integer pageSize) {
Page<UserDO> userDOPage = new Page<>();
userDOPage.setPageNum(pageNum);
userDOPage.setPageSize(pageSize);
return userService.listByCondPage(status, userDOPage);
}
}
MyBatis物理分页的代码实现的更多相关文章
- 2017.12.25 Mybatis物理分页插件PageHelper的使用(二)
参考来自: 官方文档的说明:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md 上篇博客 ...
- 【MyBatis】MyBatis自动生成代码查询之爬坑记
前言 项目使用SSM框架搭建Web后台服务,前台后使用restful api,后台使用MyBatisGenerator自动生成代码,在前台使用关键字进行查询时,遇到了一些很宝贵的坑,现记录如下.为展示 ...
- Mybatis 自动生成代码,数据库postgresql
最近做了一个项目,使用Mybatis自动生成代码,下面做一下总结,被以后参考: 一.提前准备: 1.工具类:mybatis-generator-core-1.3.2.jar 2.postgresql驱 ...
- 在MyBatis中查询数据、涉及多参数的数据访问操作、插入数据时获取数据自增长的id、关联表查询操作、动态SQL、关于配置MyBatis映射没有代码提示的解决方案
1. 单元测试 在单元测试中,每个测试方法都需要执行相同的前置代码和后置代码,则可以自定义2个方法,分别在这2个方法中执行前置代码和后置代码,并为这2个方法添加@Before和@After注解,然后, ...
- Mybatis最入门---代码自动生成(generatorConfig.xml配置)
[一步是咫尺,一步即天涯] 经过前文的叙述,各位看官是不是已经被Mybatis的强大功能给折服了呢?本文我们将介绍一个能够极大提升我们开发效率的插件:即代码自动生成.这里的代码自动生成包括,与数据库一 ...
- springboot mybatis 自动生成代码(maven+IntelliJ IDEA)
1.在pom文件中加入需要的依赖(mybatis-generator-core) 和 插件(mybatis-generator-maven-plugin) <dependency> < ...
- Mybatis使用Dao代码方式CURD
Mybatis 使用Dao代码方式进行增.删.改.查. 1.Maven的pom.xml <project xmlns="http://maven.apache.org/POM/4.0. ...
- 07.深入浅出 Spring Boot - 数据访问之Mybatis(附代码下载)
MyBatis 在Spring Boot应用非常广,非常强大的一个半自动的ORM框架. 代码下载:https://github.com/Jackson0714/study-spring-boot.gi ...
- mybatis自动生成代码插件mybatis-generator使用流程(亲测可用)
mybatis-generator是一款在使用mybatis框架时,自动生成model,dao和mapper的工具,很大程度上减少了业务开发人员的手动编码时间 坐着在idea上用maven构建spri ...
随机推荐
- 转:用STL中的vector动态开辟二维数组
用STL中的vector动态开辟二维数组 源代码:#include <iostream>#include <vector>using namespace std;int mai ...
- 根据选中不同的图元来显示不同的属性面板changePropertyPane.html
在现实生活中,我们有很多时候需要根据选中不同的东西来获取不同的属性,并且就算是同类型的东西我们有时也希望显示不同的属性,就像每个人都有不同的个性,可能会有相同点,但是不可能完全相同. 根据这个思想,我 ...
- Sql语句备份Sqlserver数据库
BACKUP DATABASE [POS_YiZhuang]TODISK = N'C:\数据库\POS_YiZhuang2016-09-20-3.bak'WITHNAME = N'POS_YiZhua ...
- C#匹配标签正则,获取标签的值
比如要获取: <color=#50cccc>头盔坐标:(-0.6, 1.0, 1.2)</color><color=#3d85c6>头盔方向(-0.2, 0.1, ...
- JavaScript OOP 之 this指向
今天给大家分享一个JavaScript OOP中关于分辨this指向对象的小技巧,很实用呦! 我们先来看一段代码: 大家能判断出func();和obj.func();这两句的this指向吗? 首先,我 ...
- java swing中Timer类的学习
最近在完成学校课程的java平时作业,要实现一个计时器,包含开始.暂停以及重置三个功能.由于老师规定要用这个timer类,也就去学习了一下,顺便记录一下. 首先呢去查了一下java手册上的东西,发现t ...
- nginx + tomcat + redis 部署项目,解决session共享问题。
最近自己搭了一套nginx的环境,集群部署了公司的一个项目,中间解决了session共享的问题.记录如下,以备日后查看. 1.环境 windows10 家庭中文版,jdk 7, tomcat 7.0. ...
- Vue源码后记-vFor列表渲染(1)
钩子函数比较简单,没有什么意思,这一节搞点大事情 => 源码中v-for的渲染过程. vue的内置指令包含了v-html.v-if.v-once.v-bind.v-on.v-show等,先从一个 ...
- JS框架设计读书笔记之-节点模块
节点的创建 浏览器提供了多种手段创建API,从流行程度依次是document.createElement.innerHTML.insertAdjacentHTML.createContextualFr ...
- win7旗舰版安装IIS
1.在控制面板中:程序->打开或关闭Windows功能 2.在弹出的对话窗中,在“Internet服务信息”中作如下勾选(打钩的为全选) 点击确定后安装. 3.安装成功后回到控制面板->系 ...