mybatis简单的分页插件

mybatis的SqlSession可以直接通过以下接口来进行分页查询:

<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

 不过该查询是基于对查询结果去子集的方式。该方法效率较低(如果只查询一条数据服务器需要返回所有满足条件的数据)。

实现思路:用mybatis插件拦截StatementHandler的prepare方法,修改Sql语句,添加分页查询语句。至于分页中的总页数查询,另执行一条select count(*)查询语句。实现较简单。

StatementHandler 的执行过程简介:

在Executor中通过下面代码创建StatementHandler:

StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

 configuration是mybatis的全局配置文件,其newStatementHandler创建过程如下:

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

 RoutingStatementHandler可以看作是非法语句处理器,具体的操作还是要委托给具体的BaseStatementHandler。interceptorChain.pluginAll用于将所有配置的myabtis插件类应用到这个接口上面。interceptorChain是一个拦截器链(职责链模式)

public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

 interceptors保存着配置mybatis的所有拦截器,分别调用拦截器对目标进行代理。也就是生成目标类的代理。例如拦截器Incet

package cn.liuyao.mybatis.test;

import java.util.Properties;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

@Intercepts({@Signature(type = StatementHandler.class,method = "prepare",args = {java.sql.Connection.class})})
public class Incet implements Interceptor{

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		return invocation.proceed();
	}

	//生成目标对象的代理
	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {

	}
}

 拦截器的plugin方法调用Plugin的wrap对目标对象包装代理。

public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

 wrap返回目标对象的代理,这样在执行特定接口的特定方法时候,能够增加自己的一些代码,类似于spring的aop功能。

Object intercept(Invocation invocation)

intercept编写额外的代码。实现自己的分页逻辑。

 由于mybatis的语句执行,最终都由StatementHandler来完成,故只要拦截该接口的prepare方法,并修改boundSql与rowBound既可以达到修改Sql与添加分页信息的效果。

Page类:

package com.ccq.framework.lang;

import java.io.Serializable;

public class Page implements Serializable{

	private static final long serialVersionUID = 9000151198919642793L;

	private int pages;
	private int pageNum;
	private int pageSize;
	
	public Page() {
		super();
		// TODO Auto-generated constructor stub
	}
	public Page(int pages, int pageNum, int pageSize) {
		super();
		this.pages = pages;
		this.pageNum = pageNum;
		this.pageSize = pageSize;
	}
	public int getPages() {
		return pages;
	}
	public void setPages(int pages) {
		this.pages = pages;
	}
	public int getPageNum() {
		return pageNum;
	}
	public void setPageNum(int pageNum) {
		this.pageNum = pageNum;
	}
	public int getPageSize() {
		return pageSize;
	}
	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}
}
 page类用于保存分页信息:当前页,页大小,所有页数。

pagePlugin:

package com.ccq.framework.plugin;

import java.util.Properties;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Intercepts({@Signature(type = StatementHandler.class,method = "prepare",args = {java.sql.Connection.class})})
public class MyPagePlugin implements Interceptor{

	private Dialect dialect;
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		
		Logger log = LoggerFactory.getLogger(MyPagePlugin.class);
		
		StatementHandler hander = (StatementHandler) invocation.getTarget();
		//对handler进行包装可以获取其私有属性
		MetaObject meta = MetaObject.forObject(hander, new DefaultObjectFactory(), 
				new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
		RowBounds rowBound = (RowBounds) meta.getValue("delegate.rowBounds");//获取分页信息
		
		if(rowBound.equals(RowBounds.DEFAULT)) {
			
			//未指定分页命令
			return invocation.proceed();
		}
		BoundSql boundSql = (BoundSql) meta.getValue("delegate.boundSql");//获取sql语句
		
		if(dialect == null) {
			
			log.debug("not assigned dialect,use mysql default");
			dialect = new MysqlDialect();
		}
		
		String buildSql = dialect.builderSql(boundSql.getSql(), rowBound.getOffset(), rowBound.getLimit());
		MetaObject boundSqlMeta = MetaObject.forObject(boundSql, new DefaultObjectFactory(), 
				new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
		
		boundSqlMeta.setValue("sql", buildSql);
		
		meta.setValue("delegate.rowBounds", RowBound.DEFAULT);//回复默认的分页信息
		return invocation.proceed();
	}

	//生成目标对象的代理
	@Override
	public Object plugin(Object target) {
		if(target instanceof StatementHandler) {
			return Plugin.wrap(target, this);
		}else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties properties) {
		if(properties.getProperty("dialect").equals("mysql")) {
			dialect = new MysqlDialect();
		}else if(properties.getProperty("dialect").equals("oracle")) {
			
			//TODO
		}
	}

}
 buildSql:
package com.ccq.framework.plugin;

import com.ccq.framework.exception.AppException;

public class MysqlDialect extends Dialect{

	@Override
	public String builderSql(String rawSql,int OFFSET,int LIMIT) {
		
		if(rawSql.endsWith(";")) {
			
			throw new AppException("Bad sql: grammer error on ; with limit");
		}
		
		StringBuffer sb = new StringBuffer(rawSql);
		//SELECT * FROM table LIMIT [offset,] rows
		sb.append(String.format(" limit %s,%s",new Object[]{OFFSET,LIMIT}));
		return sb.toString();
	}
}

 只针对mysql的sql语句。

测试:采用了的mybatis的通用单表CRUD工具,后面给链接。

相关推荐