MyBatis假分页#

参考DefaultResultSetHandler的skipRows方法。

温馨提示:部分代码请参考轻量级封装DbUtils&Mybatis之一概要

解决方案#

1)之前公司同事,亦师亦上司勇哥已经处理过分页的逻辑:自定义一个包装类包装SqlSession,完全开放SqlSession的各类访问方法,直接可通过传入RowBounds(包装offset&limit)参数完成分页逻辑。

2)参考mybatis-pagination项目。

备注:因为个人希望不要和MyBatis原有的使用方法差异太大,尽量减少自定义的处理,所以才总结自己的思路和想法,目标实际上是希望保留MyBatis自定义mapper接口即可实现Jdbc访问的特性。

温馨提示:请下载上面提到的项目,并对MyBatis分页处理有一定了解。

分页处理解决方案#

mybatis-pagination分页处理分析##

1)通过外置增加的排序和分页选项,在mapper文件中配置排序选项,通过参数控制排序的条件,而在interceptor拦截时处理分页

2)自定义Interceptor和Executor,修改了目标执行逻辑处理各类条件逻辑

优点:功能够强大

缺点:自定义的内容偏多,实现过于复杂,不知是否会受到MyBatis升级的影响

现有解决方案##

feature

1)目前暂不支持排序,后续考虑,但绝对不会考虑在mapper配置文件中定义排序条件

2)自定义Interceptor,不考虑将结果集列表包装成Page对象,保证分页逻辑和不分页的逻辑成为可选项,调用方法可共用

3)所有和查询结果集无关但有用的返回结果都包装成一个对象,存放到ThreadLocal

4)定义Mapper接口的分页方法最后一个参数类型务必是Criteria(参考下文代码实现),否则无法提供分页功能

拦截器处理流程

1)获取MetaObject,得到MappedStatement和ParameterHandler

2)判定stamentId是否匹配配置的表达式,mapper接口方法最后一个参数是否为Criteria类型,不满足判定则执行原有SQL逻辑,满足则执行实际分页处理逻辑

3)实际分页处理时,关闭原有的分页设置,将分页参数绑定到SQL上,并执行获取总记录数的方法

代码呈上#

测试样例

package org.wit.ff.jdbc;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.wit.ff.jdbc.dao.HomeTownDao;
import org.wit.ff.jdbc.query.Criteria;
import org.wit.ff.jdbc.result.CriteriaResultHolder; /**
* Created by F.Fang on 2015/11/19.
*/
@ContextConfiguration(locations = {"classpath:applicationContext-paging.xml"})
public class HomeTownDaoPagingTest extends AbstractJUnit4SpringContextTests { @Autowired
private HomeTownDao homeTownDao; @Test
public void testFind() {
// Criteria对象包装分页条件.
System.out.println(homeTownDao.find(1, new Criteria().page(1, 1)));
//System.out.println(homeTownDao.find(1, null));
// 从线程上下文中获取总页数,总记录数等信息.
try {
System.out.println(CriteriaResultHolder.get());
}finally {
CriteriaResultHolder.remove();
}
} }

HomeTownDao

package org.wit.ff.jdbc.dao;

import org.wit.ff.jdbc.model.HomeTown;
import org.wit.ff.jdbc.query.Criteria; import java.util.List; /**
* Created by F.Fang on 2015/11/17.
* Version :2015/11/17
*/
public interface HomeTownDao {
List<HomeTown> find(int id,Criteria criteria);
}

Criteria

package org.wit.ff.jdbc.query;

/**
* Created by F.Fang on 2015/11/19.
* 后续可扩展排序参数.
*/
public class Criteria { private int pageNumber; private int pageSize; public Criteria page(int pageNumber, int pageSize){
this.pageNumber = pageNumber;
this.pageSize = pageSize;
return this;
} public int getPageSize() {
return pageSize;
} public void setPageSize(int pageSize) {
this.pageSize = pageSize;
} public int getPageNumber() {
return pageNumber;
} public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
}
}

CriteriaResult

package org.wit.ff.jdbc.result;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; /**
* Created by Yong.Huang.
* Updated by F.Fang on 2015/11/19.
*/
public class CriteriaResult{ private int pageNumber; private int pageSize; private long pageCount; private long totalCount; public CriteriaResult(int pageNumber, int pageSize, long totalCount) {
this.pageNumber = pageNumber;
this.pageSize = pageSize;
this.totalCount = totalCount;
if (pageSize != 0) {
if (totalCount % pageSize == 0) {
pageCount = totalCount / pageSize;
} else {
pageCount = totalCount / pageSize + 1;
}
}
} public int getPageNumber() {
return pageNumber;
} public int getPageSize() {
return pageSize;
} public long getPageCount() {
return pageCount;
} public long getTotalCount() {
return totalCount;
} public boolean hasPrevPage() {
return pageNumber > 1 && pageNumber <= pageCount;
} public boolean hasNextPage() {
return pageNumber < pageCount;
} public boolean isFirstPage() {
return pageNumber == 1;
} public boolean isLastPage() {
return pageNumber == pageCount;
} @Override
public String toString() {
return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE);
}
}

CriteriaResultHolder

package org.wit.ff.jdbc.result;

/**
* Created by F.Fang on 2015/11/19.
*/
public class CriteriaResultHolder { private static final ThreadLocal<CriteriaResult> criteriaResult = new ThreadLocal<CriteriaResult>(); private CriteriaResultHolder(){} public static CriteriaResult get() {
return criteriaResult.get();
} public static void set(CriteriaResult value){
if(criteriaResult.get() == null && value!=null){
criteriaResult.set(value);
}
} public static void remove(){
criteriaResult.remove();
}
}

Mapper定义

<select id="find" resultType="HomeTown" >
select * from hometown
</select>

mybatis.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings> <plugins>
<plugin interceptor="org.wit.ff.jdbc.paging.MysqlPagingInterceptor">
<property name="statementRegex" value=".*find.*"/>
</plugin>
</plugins> </configuration>

Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd"> <!-- 数据源,请自行修改 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${db.driverClass}"/>
<property name="url" value="${db.jdbcUrl}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</bean> <!-- 配置 SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
<!-- 制定路径自动加载mapper配置文件 -->
<property name="mapperLocations" value="classpath:mappers/*Dao.xml"/> <!-- 配置myibatis的settings http://mybatis.github.io/mybatis-3/zh/configuration.html#settings -->
<property name="configurationProperties">
<props>
<prop key="cacheEnabled">true</prop>
</props>
</property>
<!-- 类型别名是为 Java 类型命名一个短的名字。 它只和 XML 配置有关, 只用来减少类完全 限定名的多余部分 -->
<property name="typeAliasesPackage" value="org.wit.ff.jdbc.model"/> </bean> <mybatis:scan base-package="org.wit.ff.jdbc.dao"/> </beans>

核心拦截器

若对MyBatis拦截器相关的内容有疑问,请自行谷歌or百度,好的资源太多

package org.wit.ff.jdbc.paging;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.RowBounds;
import org.wit.ff.jdbc.dialect.Dialect;
import org.wit.ff.jdbc.query.Criteria;
import org.wit.ff.jdbc.result.CriteriaResult;
import org.wit.ff.jdbc.result.CriteriaResultHolder; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Properties; /**
* Created by Yong.Huang
* Updated by F.Fang on 2015/11/19.
* Mybatis属于假分页 , 参考代码: DefaultResultSetHandler执行方法链:
* handleResultSets--> handleResultSet --> handleRowValues --> handleRowValuesForNestedResultMap
* --> skipRows --> 执行 rs.absolute跳过记录数, 实际执行的语句仍然是查询了相同数量的记录.
*/
public abstract class PagingInterceptor implements Interceptor { /**
* regex匹配statementId.
*/
protected String statementRegex; @Override
public Object intercept(Invocation invocation) throws Throwable { MetaObject metaObject = SystemMetaObject.forObject(invocation.getTarget());
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); // 匹配拦截StatementId
if (!mappedStatement.getId().matches(statementRegex)) {
return invocation.proceed();
} // 最后一个参数必须是Creteria.
ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("delegate.resultSetHandler.parameterHandler"); if (parameterHandler != null) {
Object object = parameterHandler.getParameterObject();
Object lastParam = null;
// 如果有多个参数,取最后一个参数.
if (object instanceof HashMap) {
HashMap map = (HashMap) object;
// 这个逻辑始终不太放心, 日后若有更好的实现再改.
String key = "param"+String.valueOf(map.keySet().size()/2);
lastParam = map.get(key);
} else {
lastParam = object;
} // 参数一定要匹配Criteria类型
if (lastParam == null || !(lastParam instanceof Criteria)) {
return invocation.proceed();
} Criteria criteria = (Criteria) lastParam;
StatementHandler stamentHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = stamentHandler.getBoundSql();
// 原始mapper文件中配置的Sql.
String originSql = boundSql.getSql(); int offSet = (criteria.getPageNumber() - 1) * criteria.getPageSize();
// 实际的分页sql.
String pagingSql = getDialect().getLimitString(originSql, offSet, criteria.getPageSize()); // 重新设置属性.
metaObject.setValue("delegate.boundSql.sql", pagingSql);
metaObject.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
metaObject.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT); // 获取连接参数.
Connection connection = (Connection) invocation.getArgs()[0];
// 总页数.
int totalCount = getTotalCount(connection, originSql, parameterHandler);
// 填充各属性值.
CriteriaResult result = new CriteriaResult(criteria.getPageNumber(), criteria.getPageSize(), totalCount);
CriteriaResultHolder.set(result);
}
// 执行SQL.
return invocation.proceed();
} @Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
} @Override
public void setProperties(Properties properties) {
statementRegex = properties.getProperty("statementRegex");
} public abstract Dialect getDialect(); private int getTotalCount(Connection connection, String sql, ParameterHandler parameterHandler) throws SQLException {
int result = 0;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String countSql = getDialect().getCountString(sql);
ps = connection.prepareStatement(countSql);
parameterHandler.setParameters(ps);
rs = ps.executeQuery();
if (rs.next()) {
result = rs.getInt(1);
}
} finally {
if (ps != null) {
ps.close();
}
if (rs != null) {
rs.close();
}
}
return result;
}
}
package org.wit.ff.jdbc.paging;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Signature;
import org.wit.ff.jdbc.dialect.Dialect;
import org.wit.ff.jdbc.dialect.db.MySQLDialect; import java.sql.Connection; /**
* Created by F.Fang on 2015/11/19.
*/
@Intercepts(
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})
)
public class MysqlPagingInterceptor extends PagingInterceptor{
private Dialect dialect = new MySQLDialect(); @Override
public Dialect getDialect() {
return dialect;
}
}

QA#

轻量级封装DbUtils&Mybatis之三MyBatis分页的更多相关文章

  1. 轻量级封装DbUtils&Mybatis之四MyBatis主键

    MyBatis主键 不支持对象列表存储时对自增id字段的赋值(至少包括3.2.6和3.3.0版本),如果id不是采用底层DB自增主键赋值,不必考虑此问题 温馨提示:分布式DB环境下,DB主键一般会采用 ...

  2. 轻量级封装DbUtils&Mybatis之一概要

    Why 一时兴起,自以为是的对Jdbc访问框架做了一个简单的摸底,近期主要采用Mybatis,之前也有不少采用Dbutils,因此希望能让这两个框架折腾的更好用. DbUtils:非常简单的Jdbc访 ...

  3. 轻量级封装DbUtils&Mybatis之二Dbutils

    DbUtils入门 Apache出品的极为轻量级的Jdbc访问框架,核心类只有两个:QueryRunner和ResultSetHandler. 各类ResultSetHandler: ArrayHan ...

  4. 【MyBatis】MyBatis之分页

    关于MyBatis的搭建可以参见“MyBatis的配置”,MyBatis是对JDBC底层代码的封装,关于Oracle.MySQL.SqlServer的分页可以查看Oracle.SqlServer.My ...

  5. 【MyBatis】MyBatis分页插件PageHelper的使用

    好多天没写博客了,因为最近在实习,大部分时间在熟悉实习相关的东西,也没有怎么学习新的东西,这周末学习了MyBatis的一个分页插件PageHelper,虽然没有那么的强大(我在最后会说明它的缺点),但 ...

  6. Mybatis中的分页

    Mybatis中有哪些分页方式? 数组分页:查询出全部数据,然后再list中截取需要的部分.(逻辑分页) 优点:效率高     缺点:占用内存比较高 sql分页:只从数据库中查询当前页的数据.(物理分 ...

  7. Mybatis Generator实现分页功能

    Mybatis Generator实现分页功能 分类: IBATIS2013-07-17 17:03 882人阅读 评论(1) 收藏 举报 mybatisibatisgeneratorpage分页 众 ...

  8. SpringBoot+Mybatis配置Pagehelper分页插件实现自动分页

    SpringBoot+Mybatis配置Pagehelper分页插件实现自动分页 **SpringBoot+Mybatis使用Pagehelper分页插件自动分页,非常好用,不用在自己去计算和组装了. ...

  9. SpringBoot+Mybatis+PageHelper实现分页

    SpringBoot+Mybatis+PageHelper实现分页 mybatis自己没有分页功能,我们可以通过PageHelper工具来实现分页,非常简单方便 第一步:添加依赖 <depend ...

随机推荐

  1. vue 全选多选

    html: //全选按钮 <li class="choice_fme"> <div @click="checkAll" v-bind:clas ...

  2. 【hive】null值判断

    hive用作null值的判断是不能用 = , != 来判断的 只能用is [not] null来完成 不支持ifnull()函数(mysql支持) 适用于所有数据类型 (1)条件中判断是否为空 whe ...

  3. laravel5.5种的Eloquent ORM的使用:

    控制器方法: //Eloquent ORM的使用: public function orm1() { //all() /*$students=Student::all(); dd($students) ...

  4. 第1课:接口测试和jmeter总结

    接口测试 1. 接口的分类:webService和http api接口 1) webService接口:是按照soap协议通过http传输,请求报文和返回报文都是xml格式,一般要借助工具来测试接口: ...

  5. mysql数据库的笔记

    增删改查置顶: 插入数据: 基本语法 : insert into [表名](字段名1,字段名2……) values(记录1),(记录2): insert into [表名] values(记录1),( ...

  6. 马士兵_JAVA自学之路(为那些目标模糊的码农们)

    转载自:http://blog.csdn.net/anlidengshiwei/article/details/42264301 JAVA自学之路 一:学会选择 为了就业,不少同学参加各种各样的培训. ...

  7. 安装Spring报错An error occurred while collecting items to be installed

    原因主要是eclipse和spring版本之间的匹配问题. An error occurred while collecting items to be installed session conte ...

  8. macOS 下安装SDKMAN 软件开发工具包管理器

    SDKMAN 软件开发工具包管理器的安装非常简单,只需要打开终端,执行: $ curl -s "https://get.sdkman.io" | bash 就OK了,输出类似如下: ...

  9. 《Drools7.0.0.Final规则引擎教程》第4章 注释&错误信息

    注释 像Java开发语言一样,Drools文件中也可以添加注释.注释部分Drools引擎是会将其忽略调的.单行注释使用"//",示例如下: rule "Testing C ...

  10. C# Thrift 实战开发 从PLC到Thrift再到客户端集成开发

    About Thrift: 本文并不是说明Thrift设计及原理的,直接拿Thrift来开发一个Demo程序,如果想要了解Thrift的细节,可以访问官方网站:https://thrift.apach ...