欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Mybatis 分页插件使用教程

程序员文章站 2022-04-14 15:09:12
mybatis分页切入点 mybatis内部有个plugins(插件)概念,本质上属于拦截器的思想。具体的解析可见他文mybatis拦截器原理探究。本文将在此基础上直接展...

mybatis分页切入点

mybatis内部有个plugins(插件)概念,本质上属于拦截器的思想。具体的解析可见他文mybatis拦截器原理探究。本文将在此基础上直接展示实际项目的实现代码和其他的相关解析

分页具体代码实现

首先我们可以定义方言抽象类,用于实现分页abstractdialect.java

public abstract class abstractdialect{
 /**
  * 是否支持limit和偏移量
  * @return
  */
 public abstract boolean supportslimitoffset();
 /**
  * 是否支持limit
  * @return
  */
 public abstract boolean supportslimit();
 /**
  * 获取增加了分页属性之后的sql
  * @param sql
  * @param offset
  * @param limit
  * @return
  */
 public abstract string getlimitstring(string sql, int offset, int limit);
}

再而我们就以oracle与mysql数据库的分页技术作下分别的实现

mysqldialect.java-mysql分页方言

public class mysqldialect extends abstractdialect {
 public boolean supportslimitoffset() {
  return true;
 }
 public boolean supportslimit() {
  return true;
 }
 public string getlimitstring(string sql, int offset, int limit) {
  if (offset > 0) {
   return sql + " limit " + offset + "," + limit;
  } else {
   return sql + " limit " + limit;
  }
 }
}

oracledialect.java-oracle方言实现

public class oracledialect extends adialect{
 @override
 public boolean supportslimitoffset() {
  return false;
 }
 @override
 public boolean supportslimit() {
  return false;
 }
 @override
 public string getlimitstring(string sql, int start, int limit) {
  if(start < 0){
   start = 0;
  }
  if(limit < 0){
   limit = 10;
  }
  stringbuilder pagesql = new stringbuilder(100);
  pagesql.append("select * from ( select temp.*, rownum row_id from ( ");
  pagesql.append(sql);
  pagesql.append(" ) temp where rownum <= ").append(start+limit);
  pagesql.append(") where row_id > ").append(start);
  return pagesql.tostring();
 }
}

对应的mybatis插件拦截器实现如下,拦截statementhandler#prepare(connection con)创建sql语句对象方法

paginationinterceptor.java

@intercepts({ @signature(type = statementhandler.class, method = "prepare", args = { connection.class }) })
public final class paginationinterceptor implements interceptor {
 private final static logger log = loggerfactory
  .getlogger(paginationinterceptor.class);
 private adialect dialect;
 public void setdialect(adialect dialect) {
  this.dialect = dialect;
 }
 @override
 public object intercept(invocation invocation) throws throwable {
  // 直接获取拦截的对象,其实现类routingstatementhandler
  statementhandler statementhandler = (statementhandler) invocation
   .gettarget();
  boundsql boundsql = statementhandler.getboundsql();
  // 获取元对象,主要用于获取statementhandler所关联的对象及属性
  metaobject metastatementhandler = metaobject.forobject(
   statementhandler, new defaultobjectfactory(),
   new defaultobjectwrapperfactory());
  mappedstatement mappedstmt= (mappedstatement) metastatementhandler
   .getvalue("delegate.mappedstatement".intern());
  // 只对querypagination()方法进行分页操作
  if(mappedstmt.getid().indexof("querypagination")==-1){
   return invocation.proceed();
  }
  // 重新构造分页的sql
  string originalsql = (string) metastatementhandler
   .getvalue("delegate.boundsql.sql".intern());
  metastatementhandler.setvalue("delegate.boundsql.sql".intern(), dialect
   .getlimitstring(originalsql, rowbounds.getoffset(),
    rowbounds.getlimit()));
  metastatementhandler.setvalue("delegate.rowbounds.offset".intern(),
   rowbounds.no_row_offset);
  metastatementhandler.setvalue("delegate.rowbounds.limit".intern(),
   rowbounds.no_row_limit);
  log.debug("page sql : " + boundsql.getsql());
  return invocation.proceed();
 }
 // 拦截对象
 @override
 public object plugin(object target) {
  return plugin.wrap(target, this);
 }
 @override
 public void setproperties(properties properties) {
 }
}

spring对应的xml配置可如下,以oracle分页为例子

<!-- oracle方言配置,用于oracle的分页 -->
 <bean id="paginationinterceptor"  class="com.hsnet.winner.cas.admin.core.dao.mybatis.interceptor.paginationinterceptor">
  <property name="dialect">
   <bean class="cn.cloud.winner.oss.manager.mybatis.page.oracledialect" />
  </property>
 </bean>

使用以上的代码以及配置即可完成对oracle数据库以及mysql数据库的分页操作。并且博主对其中的某个点作下解析

mybatis#metaobject-元数据对象解析

以上的代码博主之前在使用的时候,对其中的metaobject这个类很费解,其直接通过getvalue()方法便可以将所代理的对象的所关联的属性全都拿取到。我们可以跟随源码深入了解下

metaobject#forobject()

代理对象均通过此静态方法进入

public static metaobject forobject(object object, objectfactory objectfactory, objectwrapperfactory objectwrapperfactory) {
 if (object == null) {
  return systemmetaobject.null_meta_object;
 } else {
  return new metaobject(object, objectfactory, objectwrapperfactory);
 }
 }

我们可以直接观察其中的构造函数,玄机就在此处

private metaobject(object object, objectfactory objectfactory, objectwrapperfactory objectwrapperfactory) {
 this.originalobject = object;
 this.objectfactory = objectfactory;
 this.objectwrapperfactory = objectwrapperfactory;
 // 所有的属性获取均通过objectwrapper类来获取,此处主要对所代理的object对象类型进行判断
 if (object instanceof objectwrapper) {
  this.objectwrapper = (objectwrapper) object;
 } else if (objectwrapperfactory.haswrapperfor(object)) {
  this.objectwrapper = objectwrapperfactory.getwrapperfor(this, object);
 } else if (object instanceof map) {
  this.objectwrapper = new mapwrapper(this, (map) object);
 } else if (object instanceof collection) {
  this.objectwrapper = new collectionwrapper(this, (collection) object);
 } else {
  // 我们常用的便是beanwrapper
  this.objectwrapper = new beanwrapper(this, object);
 }
 }

为了理解的更为渗透,我们继续跟进,最后我们得知其会调用reflector类的构造函数

private reflector(class<?> clazz) {
 type = clazz;
 // 获取构造类
 adddefaultconstructor(clazz);
 // 获取get方法集合
 addgetmethods(clazz);
 // 获取set方法集合
 addsetmethods(clazz);
 // 获取内部属性集合
 addfields(clazz);
 readablepropertynames = getmethods.keyset().toarray(new string[getmethods.keyset().size()]);
 writeablepropertynames = setmethods.keyset().toarray(new string[setmethods.keyset().size()]);
 for (string propname : readablepropertynames) {
  caseinsensitivepropertymap.put(propname.touppercase(locale.english), propname);
 }
 for (string propname : writeablepropertynames) {
  caseinsensitivepropertymap.put(propname.touppercase(locale.english), propname);
 }
 }

由此我们便可知使用reflector代理类以及metaobject便可以遍历代理被代理类的所关联的所有属性,就拿routingstatementhandler类来说,经过上述操作后其便可以访问内部属性delegate以及delegate的内部属性configuration/objectfactory/typehandlerregistry/resultsethandler/parameterhandler/mappedstatement等属性

metaobject#getvalue()

上述阐述的是如何代理被代理类的内部属性,我们也简单的看下是如何正确的调用

public object getvalue(string name) {
 // propertytokenizer与stringtokenizer类似,只是前者写死以.为分隔符
 propertytokenizer prop = new propertytokenizer(name);
 if (prop.hasnext()) {
  metaobject metavalue = metaobjectforproperty(prop.getindexedname());
  if (metavalue == systemmetaobject.null_meta_object) {
  return null;
  } else {
  return metavalue.getvalue(prop.getchildren());
  }
 } else {
  return objectwrapper.get(prop);
 }
 }

具体的解析就不在此阐述了,如何用户想获取statementhandler所拥有的sql字符串,可通过getvalue("delegate.boundsql.sql")其中以.为分隔符并其中的属性必须是内部属性(区分大小写)。

metaobject#setvalue()

原理同metaobject#getvalue()方法

总结

以上所述是小编给大家介绍的spring mybatis 分页插件使用教程,希望对大家有所帮助