程序员人生 网站导航

深入浅出Mybatis-分页

栏目:框架设计时间:2017-03-20 09:51:41

Mybatis的分页功能很弱,它是基于内存的分页(查出所有记录再按偏移量和limit取结果),在大数据量的情况下这样的分页基本上是没有用的。本文基于插件,通过拦截StatementHandler重写sql语句,实现数据库的物理分页。本文适配的mybatis版本是3.2.2

准备

为何在StatementHandler拦截

深入浅出MyBatis-Sqlsession章节介绍了1次sqlsession的完全履行进程,从中可以知道sql的解析是在StatementHandler里完成的,所以为了重写sql需要拦截StatementHandler

MetaObject简介

在我的实现里大量使用了MetaObject这个对象,因此有必要先介绍下它。MetaObjectMybatis提供的1个的工具类,通过它包装1个对象后可以获得或设置该对象的本来不可访问的属性(比如那些私有属性)。它有个3个重要方法常常用到:

1)       MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory)

2)       Object getValue(String name)

3)       void setValue(String name, Object value)

方法1)用于包装对象;方法2)用于获得属性的值(支持OGNL的方法);方法3)用于设置属性的值(支持OGNL的方法);

插件的原理

参见深入浅出Mybatis-插件原理

 

有了上面这些基础知识的准备后,就能够我们的主题了。

拦截器签名

[java] view plain copy
  1. @Intercepts({@Signature(type =StatementHandler.class, method = "prepare", args ={Connection.class})})  
  2. publicclass PageInterceptor implementsInterceptor {  
  3. ...  
  4. }  

从签名里可以看出,要拦截的目标类型是StatementHandler(注意:type只能配置成接口类型),拦截的方法是名称为prepare参数为Connection类型的方法。

intercept的实现

[java] view plain copy
  1. public Object intercept(Invocation invocation) throws Throwable {  
  2.      StatementHandler statementHandler = (StatementHandler) invocation.getTarget();  
  3.      MetaObject metaStatementHandler = MetaObject.forObject(statementHandler,  
  4.      DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);  
  5.      // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而构成屡次代理,通过下面的两次循环  
  6.      // 可以分离出最原始的的目标类)  
  7.      while (metaStatementHandler.hasGetter("h")) {  
  8.          Object object = metaStatementHandler.getValue("h");  
  9.          metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,   
  10.          DEFAULT_OBJECT_WRAPPER_FACTORY);  
  11.      }  
  12.      // 分离最后1个代理对象的目标类  
  13.      while (metaStatementHandler.hasGetter("target")) {  
  14.          Object object = metaStatementHandler.getValue("target");  
  15.          metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,   
  16.          DEFAULT_OBJECT_WRAPPER_FACTORY);  
  17.      }  
  18.      Configuration configuration = (Configuration) metaStatementHandler.  
  19.      getValue("delegate.configuration");  
  20.      dialect = configuration.getVariables().getProperty("dialect");  
  21.      if (null == dialect || "".equals(dialect)) {  
  22.          logger.warn("Property dialect is not setted,use default 'mysql' ");  
  23.          dialect = defaultDialect;  
  24.      }  
  25.      pageSqlId = configuration.getVariables().getProperty("pageSqlId");  
  26.      if (null == pageSqlId || "".equals(pageSqlId)) {  
  27.          logger.warn("Property pageSqlId is not setted,use default '.*Page$' ");  
  28.          pageSqlId = defaultPageSqlId;  
  29.      }  
  30.      MappedStatement mappedStatement = (MappedStatement)   
  31.      metaStatementHandler.getValue("delegate.mappedStatement");  
  32.      // 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默许重写以Page结尾的  
  33.      //  MappedStatement的sql  
  34.      if (mappedStatement.getId().matches(pageSqlId)) {  
  35.          BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");  
  36.          Object parameterObject = boundSql.getParameterObject();  
  37.          if (parameterObject == null) {  
  38.              throw new NullPointerException("parameterObject is null!");  
  39.          } else {  
  40.              // 分页参数作为参数对象parameterObject的1个属性  
  41.              PageParameter page = (PageParameter) metaStatementHandler  
  42.                      .getValue("delegate.boundSql.parameterObject.page");  
  43.              String sql = boundSql.getSql();  
  44.              // 重写sql  
  45.              String pageSql = buildPageSql(sql, page);  
  46.              metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);  
  47.              // 采取物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数  
  48.              metaStatementHandler.setValue("delegate.rowBounds.offset",   
  49.              RowBounds.NO_ROW_OFFSET);  
  50.              metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);  
  51.              Connection connection = (Connection) invocation.getArgs()[0];  
  52.              // 重
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐