参考DefaultResultSetHandler的skipRows方法。html
舒适提示:部分代码请参考轻量级封装DbUtils&Mybatis之一律要java
1)以前公司同事,亦师亦上司勇哥已经处理过度页的逻辑:自定义一个包装类包装SqlSession,彻底开放SqlSession的各种访问方法,直接可经过传入RowBounds(包装offset&limit)参数完成分页逻辑。
2)参考mybatis-pagination项目。git
备注:由于我的但愿不要和MyBatis原有的使用方法差别太大,尽可能减小自定义的处理,因此才总结本身的思路和想法,目标其实是但愿保留MyBatis自定义mapper接口便可实现Jdbc访问的特性。github
舒适提示:请下载上面提到的项目,并对MyBatis分页处理有必定了解。spring
1)经过外置增长的排序和分页选项,在mapper文件中配置排序选项,经过参数控制排序的条件,而在interceptor拦截时处理分页
2)自定义Interceptor和Executor,修改了目标执行逻辑处理各种条件逻辑sql
优势:功可以强大
缺点:自定义的内容偏多,实现过于复杂,不知是否会受到MyBatis升级的影响apache
feature
1)目前暂不支持排序,后续考虑,但绝对不会考虑在mapper配置文件中定义排序条件
2)自定义Interceptor,不考虑将结果集列表包装成Page对象,保证分页逻辑和不分页的逻辑成为可选项,调用方法可共用
3)全部和查询结果集无关但有用的返回结果都包装成一个对象,存放到ThreadLocal
4)定义Mapper接口的分页方法最后一个参数类型务必是Criteria(参考下文代码实现),不然没法提供分页功能session
拦截器处理流程
1)获取MetaObject,获得MappedStatement和ParameterHandler
2)断定stamentId是否匹配配置的表达式,mapper接口方法最后一个参数是否为Criteria类型,不知足断定则执行原有SQL逻辑,知足则执行实际分页处理逻辑
3)实际分页处理时,关闭原有的分页设置,将分页参数绑定到SQL上,并执行获取总记录数的方法mybatis
测试样例app
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; } }