轻量级封装DbUtils&Mybatis之三MyBatis分页
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分页的更多相关文章
- 轻量级封装DbUtils&Mybatis之四MyBatis主键
MyBatis主键 不支持对象列表存储时对自增id字段的赋值(至少包括3.2.6和3.3.0版本),如果id不是采用底层DB自增主键赋值,不必考虑此问题 温馨提示:分布式DB环境下,DB主键一般会采用 ...
- 轻量级封装DbUtils&Mybatis之一概要
Why 一时兴起,自以为是的对Jdbc访问框架做了一个简单的摸底,近期主要采用Mybatis,之前也有不少采用Dbutils,因此希望能让这两个框架折腾的更好用. DbUtils:非常简单的Jdbc访 ...
- 轻量级封装DbUtils&Mybatis之二Dbutils
DbUtils入门 Apache出品的极为轻量级的Jdbc访问框架,核心类只有两个:QueryRunner和ResultSetHandler. 各类ResultSetHandler: ArrayHan ...
- 【MyBatis】MyBatis之分页
关于MyBatis的搭建可以参见“MyBatis的配置”,MyBatis是对JDBC底层代码的封装,关于Oracle.MySQL.SqlServer的分页可以查看Oracle.SqlServer.My ...
- 【MyBatis】MyBatis分页插件PageHelper的使用
好多天没写博客了,因为最近在实习,大部分时间在熟悉实习相关的东西,也没有怎么学习新的东西,这周末学习了MyBatis的一个分页插件PageHelper,虽然没有那么的强大(我在最后会说明它的缺点),但 ...
- Mybatis中的分页
Mybatis中有哪些分页方式? 数组分页:查询出全部数据,然后再list中截取需要的部分.(逻辑分页) 优点:效率高 缺点:占用内存比较高 sql分页:只从数据库中查询当前页的数据.(物理分 ...
- Mybatis Generator实现分页功能
Mybatis Generator实现分页功能 分类: IBATIS2013-07-17 17:03 882人阅读 评论(1) 收藏 举报 mybatisibatisgeneratorpage分页 众 ...
- SpringBoot+Mybatis配置Pagehelper分页插件实现自动分页
SpringBoot+Mybatis配置Pagehelper分页插件实现自动分页 **SpringBoot+Mybatis使用Pagehelper分页插件自动分页,非常好用,不用在自己去计算和组装了. ...
- SpringBoot+Mybatis+PageHelper实现分页
SpringBoot+Mybatis+PageHelper实现分页 mybatis自己没有分页功能,我们可以通过PageHelper工具来实现分页,非常简单方便 第一步:添加依赖 <depend ...
随机推荐
- 【Error】local variable 'xxx' referenced before assignment
此种错误涉及到变量的作用域,即全局变量和局部变量的操作. 总结如下: 内部函数,不修改全局变量可以访问全局变量 内部函数,修改同名全局变量,则python会认为它是一个局部变量 在内部函数修改同名全局 ...
- Ansible 小手册系列 十一(变量)
变量名约束 变量名称应为字母,数字和下划线. 变量应始终以字母开头. 变量名不应与python属性和方法名冲突. 变量使用 通过命令行传递变量(extra vars) ansible-playbook ...
- FortiDDoS是使用历史流量基线进行检测的
Understanding FortiDDoS Detection ModeIn Detection Mode, FortiDDoS logs events and builds traffic st ...
- Truncate a string
用瑞兹来截断对面的退路! 截断一个字符串! 如果字符串的长度比指定的参数num长,则把多余的部分用...来表示. 切记,插入到字符串尾部的三个点号也会计入字符串的长度. 但是,如果指定的参数num小于 ...
- 五.dbms_transaction(用于在过程,函数,和包中执行SQL事务处理语句.)
1.概述 作用:用于在过程,函数,和包中执行SQL事务处理语句. 2.包的组成 1).read_only说明:用于开始只读事务,其作用与SQL语句SET TRANSACTION READ ONLY完全 ...
- LeetCode OJ:Reverse Bits(旋转bit位)
Reverse bits of a given 32 bits unsigned integer. For example, given input 43261596 (represented in ...
- 【WebGL】4.光源
光的类型:所有的光都是从THREE.Light继承,分为环境光THREE.AmbientLight,点光源PointLight,聚光灯THREE.SpotLight和方向光THREE.Directio ...
- New Concept English three(13)
1原文打字 32w/m 错词27个 After her husband had gone to work, Mrs Richards sent her children to school and w ...
- Android 仿微信朋友圈查看
项目要做一个类似于这样的功能,就做了. 项目下载地址:http://download.csdn.net/detail/u014608640/9917626 一,看下效果: 二.activity类 pu ...
- LXC、LXD、Docker的区别与联系(by quqi99)
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 ( http://blog.csdn.net/quqi99 ) 容器 namespace技术用来进行做进程间的隔 ...