參考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; } }