Mybatis之使用拦截器实现分页(plugin案例)(14)

本文介绍了在Mybatis 3.4.5版本中使用拦截器实现分页的注意事项和具体步骤。首先强调了不同版本可能存在的差异,然后详细讲解了如何通过拦截器方法实现分页,特别是通过Map传递分页对象Page和查询参数对象。接着,展示了如何定义分页对象,以及在实际开发中让查询对象继承Page的策略。最后,提到了定义拦截器实现Interceptor接口,并给出了SqlMapConfig.xml中配置拦截器的代码片段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、使用拦截器需要注意的事项

使用的Mybatis版本是3.4.5,不同的版本略微有不同。

注意事项:

1、要拦截什么样的对象
2、拦截对象的什么行为
3、什么时候拦截
分页拦截的原理:
在SQL预编译之前,将SQL修改,对SQL进行封装,加上分页的语句,再将修改过后的SQL语句塞回去。

拦截的方法如下:该方法位于org.apache.ibatis.executor.statement


二、MyBatis使用拦截器实现分页具体代码

下面的实现方式是通过使用Map的方式传递参数,把分页对象Page和实际的查询参数的对象分开传递,在实际开发的时候可以让查询对象继承Page,根据需要获取参数。

1、第一步:定义分页对象

package com.imooc.entity;

/**
 * 分页对应的实体类
 */
public class Page {
	/**
	 * 总条数
	 */
	private int totalNumber;
	/**
	 * 当前第几页
	 */
	private int currentPage;
	/**
	 * 总页数
	 */
	private int totalPage;
	/**
	 * 每页显示条数
	 */
	private int pageNumber = 5;
	/**
	 * 数据库中limit的参数,从第几条开始取
	 */
	private int dbIndex;
	/**
	 * 数据库中limit的参数,一共取多少条
	 */
	private int dbNumber;
	
	/**
	 * 根据当前对象中属性值计算并设置相关属性值
	 */
	public void count() {
		// 计算总页数
		int totalPageTemp = this.totalNumber / this.pageNumber;
		int plus = (this.totalNumber % this.pageNumber) == 0 ? 0 : 1;
		totalPageTemp = totalPageTemp + plus;
		if(totalPageTemp <= 0) {
			totalPageTemp = 1;
		}
		this.totalPage = totalPageTemp;
		
		// 设置当前页数
		// 总页数小于当前页数,应将当前页数设置为总页数
		if(this.totalPage < this.currentPage) {
			this.currentPage = this.totalPage;
		}
		// 当前页数小于1设置为1
		if(this.currentPage < 1) {
			this.currentPage = 1;
		}
		
		// 设置limit的参数
		this.dbIndex = (this.currentPage - 1) * this.pageNumber;
		this.dbNumber = this.pageNumber;
	}

	public int getTotalNumber() {
		return totalNumber;
	}

	public void setTotalNumber(int totalNumber) {
		this.totalNumber = totalNumber;
		this.count();
	}

	public int getCurrentPage() {
		return currentPage;
	}

	public void setCurrentPage(int currentPage) {
		this.currentPage = currentPage;
	}

	public int getTotalPage() {
		return totalPage;
	}

	public void setTotalPage(int totalPage) {
		this.totalPage = totalPage;
	}

	public int getPageNumber() {
		return pageNumber;
	}

	public void setPageNumber(int pageNumber) {
		this.pageNumber = pageNumber;
		this.count();
	}

	public int getDbIndex() {
		return dbIndex;
	}

	public void setDbIndex(int dbIndex) {
		this.dbIndex = dbIndex;
	}

	public int getDbNumber() {
		return dbNumber;
	}

	public void setDbNumber(int dbNumber) {
		this.dbNumber = dbNumber;
	}
}

2、第二步:定义SQL语句

注意:SQL语句中没有与分页相关的内容

<select id="queryMessageListByPage" parameterType="java.util.Map" resultMap="MessageResult">
   select <include refid="columns"/> from MESSAGE
   <where>
    <if test="message.command != null and !"".equals(message.command.trim())">
	    and COMMAND=#{message.command}
	</if>
	<if test="message.description != null and !"".equals(message.description.trim())">
	   and DESCRIPTION like '%' #{message.description} '%'
	</if>
   </where>
   order by ID
</select>
3、定义DAO接口

注意:这里使用的是动态Mapper的实现方式

package com.imooc.dao;

import java.util.List;
import java.util.Map;

import com.imooc.bean.Message;

/**
 * 与Message配置文件相对应的接口
 */
public interface IMessage {
	
	/**
	 * 根据查询条件分页查询消息列表
	 */
	public List<Message> queryMessageListByPage(Map<String,Object> parameter);
}

4、第一步:定义拦截器实现Mybatis的org.apache.ibatis.plugin.Interceptor接口,具体内容详细见注释

package com.imooc.interceptor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
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.SystemMetaObject;

import com.imooc.entity.Page;

/**
 * 分页拦截器,需要实现mybatis的拦截器接口
 * 下面的注解解释:
 * @Signature 用于指定要拦截的类中的某个方法(注意方法可能重载),
 * 				不同版本的mybatis该方法可能不一样
 * 		type:用于指定拦截哪个接口
 * 		method:用于指定拦截哪个方法
 * 		args:指定被拦截的方法的参数
 * 该类中的三个方法的执行顺序:
 * 		1、setProperties:获取配置文件中的值
 * 		2、plugin:对不需要进行分页的SQL语句进行过滤
 * 		3、intercept:执行拦截的逻辑
 */
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class, Integer.class})})
public class PageInterceptor implements Interceptor {
	
	private String test;
	
	/**
	 * 该方法拦截的是以ByPage结尾的方法
	 */
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		//获取被拦截的对象,这里获取到的对象实现了StatementHandler接口
		StatementHandler statementHandler = 
				(StatementHandler)invocation.getTarget();
		//将statementHandler进行包装,进行包装之后,
		//可以将metaObject看成是statementHandler
		//注意forObject方法不同版本不同,这里使用的3.4.5版本,最后一个参数需要这么写
		MetaObject metaObject = MetaObject.forObject(statementHandler, 
				SystemMetaObject.DEFAULT_OBJECT_FACTORY, 
				SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, 
				new DefaultReflectorFactory());
		//进行上述包装的目的是因为metaObject可以很容易的获取属性的值
		//这里是根据StatementHandler的子类层次路由拿到MappedStatement,
		//可以理解为固定写法
		MappedStatement mappedStatement = (MappedStatement)metaObject
				.getValue("delegate.mappedStatement");
		// 配置文件中SQL语句的ID
		String id = mappedStatement.getId();
		if(id.matches(".+ByPage$")) {
			BoundSql boundSql = statementHandler.getBoundSql();
			// 原始的SQL语句
			String sql = boundSql.getSql();
			// 查询总条数的SQL语句
			String countSql = "select count(*) from (" + sql + ")a";
			//获取到参数的第一个参数
			Connection connection = (Connection)invocation.getArgs()[0];
			PreparedStatement countStatement = connection.prepareStatement(countSql);
			ParameterHandler parameterHandler = (ParameterHandler)metaObject
					.getValue("delegate.parameterHandler");
			//设置查询总条数的SQL语句的总条数
			parameterHandler.setParameters(countStatement);
			ResultSet rs = countStatement.executeQuery();
			//获取传递给SQL语句的参数
			Map<?,?> parameter = (Map<?,?>)boundSql.getParameterObject();
			//获取到page参数
			Page page = (Page)parameter.get("page");
			//设置page的总条数
			if(rs.next()) {
				page.setTotalNumber(rs.getInt(1));
			}
			// 改造后带分页查询的SQL语句
			String pageSql = sql + " limit " + page.getDbIndex() + "," 
			+ page.getDbNumber();
			//设置需要执行的分页SQL,这里采用OGNL的写法
			metaObject.setValue("delegate.boundSql.sql", pageSql);
		}
		//释放主权,最终使用拦截器调用instantiateStatement
		return invocation.proceed();
	}

	/**
	 * @param target 被拦截的对象,例如拦住一条SQL语句,
	 * 询问需不需要进行分页
	 * 该方法的作用是判断每条SQL需不需要进行分页
	 * 不需要分页的方法会经过plugin方法,
	 * 但是不会经过intercept方法
	 */
	@Override
	public Object plugin(Object target) {
		System.out.println(this.test);
		/**
		 * 将类PageInterceptor比作代理公司,
		 * this就相当于该公司的业务员
		 */
		return Plugin.wrap(target, this);
	}

	/**
	 * 该方法的作用是拿到配置文件中的值
	 */
	@Override
	public void setProperties(Properties properties) {
		//获取配置文件中的值
		this.test = properties.getProperty("test");
	}

}

5、在SqlMapConfig.xml中进行拦截器的配置

<!-- 配置拦截器 -->
<plugins>
<!-- 配置分页拦截器 -->
<plugin interceptor="com.imooc.interceptor.PageInterceptor">
  		<!-- 配置属性,供拦截器获取值 -->
  		<property name="test" value="abc"/>
  	</plugin>
</plugins>
6、页面代码

这里只提供分页处的代码。

<div class='page fix'>
	共 <b>${page.totalNumber}</b> 条
	<c:if test="${page.currentPage != 1}">
	<a href="javascript:changeCurrentPage('1')" class='first'>首页</a>
	<a href="javascript:changeCurrentPage('${page.currentPage-1}')" class='pre'>上一页</a>
	</c:if>
		当前第<span>${page.currentPage}/${page.totalPage}</span>页
	<c:if test="${page.currentPage != page.totalPage}">
	<a href="javascript:changeCurrentPage('${page.currentPage+1}')" class='next'>下一页</a>
	<a href="javascript:changeCurrentPage('${page.totalPage}')" class='last'>末页</a>
	</c:if>
		跳至 <input id="currentPageText" type='text' value='${page.currentPage}' class='allInput w28' /> 页 
<a href="javascript:changeCurrentPage($('#currentPageText').val())" class='go'>GO</a>
</div>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值