自己动手写mybatis分页插件

刚开始项目,需要用到mybatis分页,网上看了很多插件,其实实现原理基本都大同小异,但是大部分都只给了代码,注释不全,所以参考了很多篇文章(每篇文章偷一点代码,评出来自己的,半抄袭),才自己模仿着写出了一个适合自己项目的分页插件,话不多说,直接上代码,相比大部分文章,注释算很完整了

最重要的拦截器

package com.dnkx.interceptor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
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.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import com.dnkx.pojo.Page;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。 
 * 利用拦截器实现Mybatis分页的原理: 
 * 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的Sql语句 
 * 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。在Mybatis中Statement语句是通过RoutingStatementHandler对象的 
 * prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用 
 * StatementHandler对象的prepare方法,即调用invocation.proceed()。 
 * 对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好的参数和设 
 * 置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记录数的统计。 
 * 
 * 解释一下插件中可能要用到的几个类:
 * MetaObject:mybatis提供的一个基于返回获取属性值的对象的类
 * BoundSql : 在这个里面可以获取都要执行的sql和执行sql要用到的参数
 * MappedStatement : 这个可以得到当前执行的sql语句在xml文件中配置的id的值
 * RowBounds : 是mybatis内存分页要用到的。
 * ParameterHandler : 是mybatis中用来替换sql中?出现的值的.
 * 
 * @author 李小拐 2016年11月9日 10:59:04
 */  
@Intercepts({
	@Signature(type=StatementHandler.class,method="prepare",args={Connection.class}),
	@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class PageInterceptor implements Interceptor{
	private final Logger logger = LoggerFactory.getLogger(getClass());
	 //拦截分页关键字
	 private static final String SELECT_ID="page";
	 //插件运行的代码,它将代替原有的方法,要重写最重要的intercept了
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		if (invocation.getTarget() instanceof StatementHandler) {
			//这里我们有一个设定  如果查询方法含有Page 就进行分页 其他方法无视
			//所以就要获取方法名
			StatementHandler statementHandler=(StatementHandler)invocation.getTarget();
			MetaObject metaObject=SystemMetaObject.forObject(statementHandler);
			MappedStatement mappedStatement=(MappedStatement)metaObject.getValue("delegate.mappedStatement");
			String selectId=mappedStatement.getId();
			String methorName=selectId.substring(selectId.lastIndexOf(".")+1).toLowerCase();
			//然后判断下 如果含有Page 就获取sql
			if(methorName.contains(SELECT_ID)){
				
				BoundSql boundSql=(BoundSql)metaObject.getValue("delegate.boundSql");
				//分页参数作为参数对象parameterObject的一个属性  
				String sql=boundSql.getSql();
				//logger.info("获取到的sql:{}",sql);
				HashMap<String, Object> map=(HashMap<String, Object>)(boundSql.getParameterObject());
				//Page page=(Page)(boundSql.getParameterObject());
				Page page=(Page)map.get("page");
				// 重写sql  
		        String countSql=concatCountSql(sql);
		        String pageSql=concatPageSql(sql,page);
		        
		        //logger.info("重写的 select sql:{}",pageSql);
		        
		        Connection connection = (Connection) invocation.getArgs()[0];  
		        
		        PreparedStatement countStmt = null;  
		        ResultSet rs = null;  
		        int totalCount = 0;  
		        try { 
		            countStmt = connection.prepareStatement(countSql); 
		            setParameters(countStmt,mappedStatement,boundSql,boundSql.getParameterObject());
		            rs = countStmt.executeQuery();  
		            if (rs.next()) {  
		                totalCount = rs.getInt(1);
		            } 
		            
		        } catch (SQLException e) {  
		            logger.error("SQLException",e);
		        } finally {  
		            try {  
		                rs.close();  
		                countStmt.close();  
		            } catch (SQLException e) {  
		               logger.error("SQLException", e);
		            }  
		        }  
		        
		        metaObject.setValue("delegate.boundSql.sql", pageSql);            
		      
		        //绑定count
		        page.setNumCount(totalCount);
			}
		}
		return invocation.proceed();
	}
	
	// 拦截类型StatementHandler,重写plugin方法
	@Override
	public Object plugin(Object target) {
		if (target instanceof StatementHandler) {
			return Plugin.wrap(target, this);
		}else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties properties) {
		
		
	}
	
	//改造sql
	public String concatCountSql(String sql){
        //StringBuffer sb=new StringBuffer("select count(*) from ");
        /*sql=sql.toLowerCase();
        
        if(sql.lastIndexOf("order")>sql.lastIndexOf(")")){
            sb.append(sql.substring(sql.indexOf("from")+4, sql.lastIndexOf("order")));
        }else{
            sb.append(sql.substring(sql.indexOf("from")+4));
        }*/
		StringBuffer sb=new StringBuffer();
		sql=sql.toLowerCase();
        if(sql.lastIndexOf("order by")>0){
            sql=sql.substring(0,sql.indexOf("order by"));
        }
        sb.append("select count(*) from ("+sql+") tmp");
        return sb.toString();
    }
    
	
    public String concatPageSql(String sql,Page page){
        StringBuffer sb=new StringBuffer();
        sb.append(sql);
        sb.append(" limit ").append(page.getPageBegin()).append(" , ").append(page.getPageSize());
        return sb.toString();
    }
    
    /**
	 * 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.DefaultParameterHandler
	 * @param ps
	 * @param mappedStatement
	 * @param boundSql
	 * @param parameterObject
	 * @throws SQLException
	 */
	private void setParameters(PreparedStatement ps,MappedStatement mappedStatement,BoundSql boundSql,Object parameterObject) throws SQLException {
		ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings != null) {
			Configuration configuration = mappedStatement.getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
			MetaObject metaObject = parameterObject == null ? null: configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					PropertyTokenizer prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)&& boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
						}
					} else {
						value = metaObject == null ? null : metaObject.getValue(propertyName);
					}
					TypeHandler typeHandler = parameterMapping.getTypeHandler();
					if (typeHandler == null) {
						throw new ExecutorException("There was no TypeHandler found for parameter "+ propertyName + " of statement "+ mappedStatement.getId());
					}
					typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
				}
			}
		}
	}

}


分页对象Page类

package com.dnkx.pojo;

import java.util.HashMap;
import java.util.Map;

/**
 * 
 * 分页查询辅助类
 * @author 李小拐 2016年11月9日 13:55:37
 */
public class Page {
	//----------分页-----------	
	private int pageSize;//每页显示条数
	private int pageCurrentPage;//第几页
	private int pageBegin;//开始位置
	private int numCount;//总条数
	private int pageTotal;//总条数
	
	private String orderField = "";//控制排序页面显示的
	private String orderDirection = "";
	
	public Page(){
		
	}
	
	public Page(int pageSize, int pageCurrentPage) {
		super();
		this.pageSize = pageSize;
		this.pageCurrentPage = pageCurrentPage;
	}
	
	public Page(Map<String, String> map){
		if(map.get("pageNum")!=null){
			this.setPageCurrentPage(this.pageCurrentPage = Integer.parseInt(map.get("pageNum")));//要查询的页数
		}else{
			this.setPageCurrentPage(1);//设置初始值
		}
		
		if(map.get("numPerPage")!=null){
			this.setPageSize(Integer.parseInt(map.get("numPerPage")));//每页显示条数
		}else{
			this.setPageSize(5);//设置初始值
		}
		
		if(map.get("orderField")!=null){
			this.setOrderField(map.get("orderField"));
		}
		
		if(map.get("orderDirection")!=null){
			this.setOrderDirection(map.get("orderDirection"));
		}
		
	}

	public int getPageCurrentPage() {
		return pageCurrentPage;
	}
	public void setPageCurrentPage(int pageCurrentPage) {
		this.pageCurrentPage = pageCurrentPage;
	}
	public int getNumCount() {
		return numCount;
	}
	public void setNumCount(int numCount) {
		this.numCount = numCount;
	}
	public int getPageTotal() {
		return (numCount%pageSize>0)?(numCount/pageSize+1):(numCount/pageSize);
	}
	public void setPageTotal(int pageTotal) {
		this.pageTotal = pageTotal;
	}
	public int getPageSize() {
		return pageSize;
	}
	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}
	public int getPageBegin() {
		return pageSize*(pageCurrentPage-1);
	}
	public void setPageBegin(int pageBegin) {
		this.pageBegin = pageBegin;
	}
	
	public String getOrderField() {
		return orderField;
	}

	public void setOrderField(String orderField) {
		this.orderField = orderField;
	}

	public String getOrderDirection() {
		return orderDirection;
	}

	public void setOrderDirection(String orderDirection) {
		this.orderDirection = orderDirection;
	}

	public static Page getPage(int pageSize, int pageCurrentPage){
		return new Page(pageSize,pageCurrentPage);
	}

	public static Page getPage(Map map){
		return new Page(map);
	}
	
}

Controller里面调用方式

public String list(HttpServletRequest request) {
		long a=System.currentTimeMillis();
		HashMap<String,Object> map=GetRequestMap.getMap(request);//自己封装的方法,取request的参数
		Page page= Page.getPage(map);//初始化page
		map.put("page", page);//把page对象放入参数集合(这个map是mybatis要用到的,包含查询条件,排序,分页等)
		//控制排序页面显示的
		map.put(map.get("orderField")+"", map.get("orderDirection"));
		List<Employee> list=employeeService.getListPage(map);
		request.setAttribute("emlist", list);
		request.setAttribute("page", page);
		request.setAttribute("map", map);
		//取page相关属性
		page.getNumCount();//总条数
		page.getPageTotal();//总页数
		long b=System.currentTimeMillis();
		System.out.println("---------耗时:"+(b-a)+"ms");
		return "basic/employee_list";
	}

最后,spring里面配置插件

 <bean id="PageInterector" class="com.dnkx.interceptor.PageInterceptor"></bean>
      
        <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->  
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <!-- 自动扫描mapping.xml文件 -->
            <property name="mapperLocations" value="classpath:com/dnkx/mapping/*.xml"></property>  
            <property name="plugins">
            	<ref bean="PageInterector"/>
            </property>
        </bean>  

好了,到此结束,本文仅供参考!也期待大神提意见






  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值