mybatis 分页插件(物理分页)

在Mybatis中插件最经常使用的是作为分页插件,接下来我们通过实现Interceptor来完成一个分页插件。

虽然Mybatis也提供了分页操作,通过在sqlSession的接口函数中设置RowBounds,给RowBounds设置初值(RowBounds源码)来实现逻辑分页,其实现原理就是通过sql查询所有的结果,并将结果放到List中,然后根据RowBouds的limit和offset数值来返回最后的数据,这种逻辑分页在数据量比较大的情况下对性能是有影响的。虽然我们可以自己写带有分页语句的sql来实现物理分页,如mysql下:select * from table limit 10 offset 1,来获得分页结果,但不同的数据库产品(mysql、oracle、sqlserver和oracle)对应的分页语句并不相同,如果要同时兼容所有的数据库产品,需要开发人员在每个分页sql中都编码几种数据库产品的分页语句,这样的开发效率实在是太低了,Mybatis的插件Interceptor给了我们一种根据数据库类型自动组装sql语句的方法。

PageInterceptor源码如下:

[java]  view plain  copy
  1. package com.tianjunwei.page;  
  2.   
  3. import java.lang.reflect.Constructor;  
  4. import java.sql.Connection;  
  5. import java.sql.DatabaseMetaData;  
  6. import java.util.List;  
  7. import java.util.Properties;  
  8.   
  9. import org.apache.ibatis.executor.Executor;  
  10. import org.apache.ibatis.mapping.BoundSql;  
  11. import org.apache.ibatis.mapping.MappedStatement;  
  12. import org.apache.ibatis.mapping.ParameterMapping;  
  13. import org.apache.ibatis.mapping.SqlSource;  
  14. import org.apache.ibatis.plugin.Interceptor;  
  15. import org.apache.ibatis.plugin.Intercepts;  
  16. import org.apache.ibatis.plugin.Invocation;  
  17. import org.apache.ibatis.plugin.Plugin;  
  18. import org.apache.ibatis.plugin.Signature;  
  19. import org.apache.ibatis.session.ResultHandler;  
  20. import org.apache.ibatis.session.RowBounds;  
  21. import org.apache.ibatis.mapping.MappedStatement.Builder;  
  22.   
  23. import com.tianjunwei.page.dialect.Dialect;  
  24. import com.tianjunwei.page.dialect.DialectFactory;  
  25.   
  26. /* 
  27.  * 分页插件我们只需要拦截Executor的query方法即可,在执行sql语句之前组装新的分页sql语句 
  28.  */  
  29. @Intercepts({@Signature(  
  30.         type= Executor.class,  
  31.         method = "query",  
  32.         args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})  
  33.     public class PageInterceptor implements Interceptor{  
  34.         String dialectClass;  
  35.         boolean asyncTotalCount = false;  
  36.         String dataBaseType=null;  
  37.         public static ThreadLocal<RowBounds> PageRowBounds = new ThreadLocal<RowBounds>();  
  38.   
  39.         @SuppressWarnings({"rawtypes""unchecked"})  
  40.         public Object intercept(final Invocation invocation) throws Throwable {  
  41.             //Executor的实现类  
  42.             final Executor executor = (Executor) invocation.getTarget();  
  43.             //Executor的query函数的参数  
  44.             final Object[] queryArgs = invocation.getArgs();  
  45.             final MappedStatement ms = (MappedStatement)queryArgs[0];  
  46.             final Object parameter = queryArgs[1];  
  47.             //rowBounds中有分页语句的limit和offset值  
  48.             RowBounds rowBounds = (RowBounds)queryArgs[2];  
  49.               
  50.             if((PageRowBounds.get() != null)&&(PageRowBounds.get().getLimit() != RowBounds.NO_ROW_LIMIT || PageRowBounds.get().getOffset() != RowBounds.NO_ROW_OFFSET)){  
  51.                 rowBounds = PageRowBounds.get();  
  52.             }  
  53.               
  54.             //如果不需要分页操作,直接返回,rowBounds为默认值时  
  55.             if(rowBounds.getOffset() == RowBounds.NO_ROW_OFFSET  
  56.                     && rowBounds.getLimit() == RowBounds.NO_ROW_LIMIT){  
  57.                 return invocation.proceed();  
  58.             }  
  59.               
  60.             //根据不同的数据库获取不到的分页方言来  
  61.             if(dialectClass == null || "".equals(dialectClass)){  
  62.               
  63.                 //判断数据源选择方言,暂时支持mysql、oracle、postgresql和sql server 2005 2008及2012  
  64.                 Connection connection = executor.getTransaction().getConnection();  
  65.                 DatabaseMetaData databaseMetaData = null;  
  66.                 if(connection != null){  
  67.                     databaseMetaData = connection.getMetaData();  
  68.                 }else {  
  69.                     throw new Exception("connection is null");  
  70.                 }  
  71.                    
  72.                 String databaseProductName = databaseMetaData.getDatabaseProductName();  
  73.                 if( dataBaseType == null || "".equals(dataBaseType)){  
  74.                     dataBaseType = databaseProductName;  
  75.                 }  
  76.                 //通过xml方言的配置来获得方言类  
  77.                 if(databaseProductName != null && !("".equals(dataBaseType))){  
  78.                       
  79.                     dialectClass = DialectFactory.getDialectClass(dataBaseType,databaseProductName);  
  80.                   
  81.                 }else{  
  82.                     throw new Exception("the property of dialect is null");  
  83.                 }  
  84.                 setDialectClass(dialectClass);  
  85.             }  
  86.             final Dialect dialect;  
  87.             try {  
  88.                 //初始化分页方言类  
  89.                 Class clazz = Class.forName(dialectClass);  
  90.                 Constructor constructor = clazz.getConstructor(new Class[]{MappedStatement.class, Object.class, RowBounds.class});  
  91.                 dialect = (Dialect)constructor.newInstance(new Object[]{ms, parameter, rowBounds});  
  92.               
  93.             } catch (Exception e) {  
  94.                 throw new ClassNotFoundException("Cannot create dialect instance: "+dialectClass,e);  
  95.             }  
  96.             final BoundSql boundSql = ms.getBoundSql(parameter);  
  97.             //创建新的MappedStatement,此时的sql语句已经是符合数据库产品的分页语句  
  98.             //dialect.getPageSQL()获得分页语句  
  99.             //dialect.getParameterMappings(), dialect.getParameterObject(),添加了两个参数及其值,两个参数为_limit和_offset  
  100.             queryArgs[0] = copyFromNewSql(ms,boundSql,dialect.getPageSQL(), dialect.getParameterMappings(), dialect.getParameterObject());  
  101.             //sql语句的参数集合  
  102.             queryArgs[1] = dialect.getParameterObject();  
  103.             //设置为不分页,由新的sql语句进行物理分页  
  104.             queryArgs[2] = new RowBounds(RowBounds.NO_ROW_OFFSET,RowBounds.NO_ROW_LIMIT);  
  105.             return invocation.proceed();  
  106.         }  
  107.           
  108.         //这个方法是用于mybatis接口编程过程中显示的指定分页参数  
  109.         public static void setPage(int pageNumber,int pageSize){  
  110.             RowBounds pageRowBounds = null;  
  111.             if(pageNumber > 0)  
  112.                 pageRowBounds = new RowBounds((pageNumber-1)*pageSize, pageSize);  
  113.             else {  
  114.                 pageRowBounds = new RowBounds(0, pageSize);  
  115.             }  
  116.             PageRowBounds.set(pageRowBounds);  
  117.         }  
  118.           
  119.         //创建新的MappedStatement  
  120.         private MappedStatement copyFromNewSql(MappedStatement ms, BoundSql boundSql,  
  121.                                                String sql, List<ParameterMapping> parameterMappings, Object parameter){  
  122.             //根据新的分页sql语句创建BoundSql  
  123.             BoundSql newBoundSql = copyFromBoundSql(ms, boundSql, sql, parameterMappings, parameter);  
  124.             //根据newBoundSql创建新的MappedStatement  
  125.             return copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));  
  126.         }  
  127.           
  128.         //根据新的分页sql语句创建BoundSql  
  129.         private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql,  
  130.                 String sql, List<ParameterMapping> parameterMappings,Object parameter) {  
  131.             BoundSql newBoundSql = new BoundSql(ms.getConfiguration(),sql, parameterMappings, parameter);  
  132.             for (ParameterMapping mapping : boundSql.getParameterMappings()) {  
  133.                 String prop = mapping.getProperty();  
  134.                 if (boundSql.hasAdditionalParameter(prop)) {  
  135.                     newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));  
  136.                 }  
  137.             }  
  138.             return newBoundSql;  
  139.         }  
  140.   
  141.         //根据newBoundSql创建新的MappedStatement  
  142.         private MappedStatement copyFromMappedStatement(MappedStatement ms,SqlSource newSqlSource) {  
  143.             Builder builder = new Builder(ms.getConfiguration(),ms.getId(),newSqlSource,ms.getSqlCommandType());  
  144.               
  145.             builder.resource(ms.getResource());  
  146.             builder.fetchSize(ms.getFetchSize());  
  147.             builder.statementType(ms.getStatementType());  
  148.             builder.keyGenerator(ms.getKeyGenerator());  
  149.             if(ms.getKeyProperties() != null && ms.getKeyProperties().length !=0){  
  150.                 StringBuffer keyProperties = new StringBuffer();  
  151.                 for(String keyProperty : ms.getKeyProperties()){  
  152.                     keyProperties.append(keyProperty).append(",");  
  153.                 }  
  154.                 keyProperties.delete(keyProperties.length()-1, keyProperties.length());  
  155.                 builder.keyProperty(keyProperties.toString());  
  156.             }  
  157.             //setStatementTimeout()  
  158.             builder.timeout(ms.getTimeout());  
  159.             //setStatementResultMap()  
  160.             builder.parameterMap(ms.getParameterMap());  
  161.             //setStatementResultMap()  
  162.             builder.resultMaps(ms.getResultMaps());  
  163.             builder.resultSetType(ms.getResultSetType());  
  164.             //setStatementCache()  
  165.             builder.cache(ms.getCache());  
  166.             builder.flushCacheRequired(ms.isFlushCacheRequired());  
  167.             builder.useCache(ms.isUseCache());  
  168.             return builder.build();  
  169.         }  
  170.   
  171.         public Object plugin(Object target) {  
  172.             return Plugin.wrap(target, this);  
  173.         }  
  174.   
  175.         /** 
  176.          * @Title: setProperties  
  177.          * @Description: 方言插件配置时设置的参数 
  178.          * @param properties 参数 
  179.          * @return  void   
  180.          * @2016年1月13日下午3:54:47 
  181.          */  
  182.         public void setProperties(Properties properties) {  
  183.             dataBaseType = properties.getProperty("dialectType");  
  184.         }  
  185.           
  186.         public static class BoundSqlSqlSource implements SqlSource {  
  187.             BoundSql boundSql;  
  188.             public BoundSqlSqlSource(BoundSql boundSql) {  
  189.                 this.boundSql = boundSql;  
  190.             }  
  191.             public BoundSql getBoundSql(Object parameterObject) {  
  192.                 return boundSql;  
  193.             }  
  194.         }  
  195.   
  196.         public void setDialectClass(String dialectClass) {  
  197.             this.dialectClass = dialectClass;  
  198.         }  
  199.   
  200.         public void setDataBaseType(String dataBaseType) {  
  201.             this.dataBaseType = dataBaseType;  
  202.         }  
  203.           
  204.   
  205. }  

猜你喜欢

转载自blog.csdn.net/suchahaerkang/article/details/80258453