mybatis分页插件

前言

其实吧,这个分页的封装是我从mybatis实战上抄的,然后又重构了下代码,形成了自己的。现在之所以会记录一下,主要原因是出现了质变———对foreach的支持,而解决这个问题的过程中,我感觉,应该基本上使用上没有多少局限行了。下面说说实际的吧。

设计

基本的设计思路,是使用mybatis插件,首先是下面这一串注解:

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

它说明了我们在什么样的生命周期以什么样的参数来进行拦截,具体的记不太清了,有时间再回来整理这个注解的详细内容吧。然后,我们在里面自动得多进行一次count请求,由于结果集不好改变,所以我们塞到入参里返回,然后再自动得把分页参数加到查询的sql末尾。由于我们只用mysql,所以就不做数据库方言了。

实现

不多说了,实现了Interceptor要重写一个方法,如下:

		// 获取方法名
DEFAULT_OBJECT_FACTORY,DEFAULT_OBJECT_WRAPPER_FACTORY);

		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
		// 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环可以分离出最原始的的目标类)
		while (metaStatementHandler.hasGetter("h")) {
			Object object = metaStatementHandler.getValue("h");
			metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
		}
		// 分离最后一个代理对象的目标类
		while (metaStatementHandler.hasGetter("target")) {
			Object object = metaStatementHandler.getValue("target");
			metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
		}

		//获取selectid
		MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
		String selectId = mappedStatement.getId();
		int count = 0;
		// 如果以指定词结尾则处理
		if (selectId.endsWith(this.endWord)) {
			BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
			//获取参数
			Map params = (Map) (boundSql.getParameterObject());

			count = this.getTotal(invocation, metaStatementHandler, boundSql);
			params.put("count", count);
			return this.getLimitedData(invocation, metaStatementHandler, boundSql, params);

		}
		Object result = invocation.proceed();
		return result;

前面的一大截都是获取对应的statement,然后就是获取selectId,然后是判断selectId是不是要拦截的,如果是,就先进行一次count,再查询数据。getTotal方法就是获取总条数的方法,返回值就是总条数,会直接塞到入参里,具体代码如下:

private int getTotal(Invocation ivt, MetaObject metaStatementHandler, BoundSql boundSql) throws SQLException, NoSuchFieldException, IllegalAccessException {
		//获取当前的mappedStatement
		MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
		//配置对象
		Configuration cfg = mappedStatement.getConfiguration();
		//当前需要执行的sql
		String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		//改写sql
		String countSql = this.concatCountSql(sql);
		//获取拦截方法参数,我们知道是Connection对象
		Connection connection = (Connection) ivt.getArgs()[0];
		PreparedStatement ps = null;
		int total = 0;
		try {

			//预编译统计总数sql
			ps = connection.prepareStatement(countSql);
			//构建统计总数BoundSql
			BoundSql countBoundSql = new BoundSql(cfg, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());

			//将原来的metaParameters复制到新的countBoundSql中
			Field metaParamsField = ReflectUtil.getFieldByFieldName(countBoundSql, "metaParameters");
			if (metaParamsField != null) {
				MetaObject mo = (MetaObject) ReflectUtil.getValueByFieldName(boundSql, "metaParameters");
				ReflectUtil.setValueByFieldName(countBoundSql, "metaParameters", mo);
			}

			//将原来的additionalParameters复制到新的countBoundSql中
			Field additionalParametersField = ReflectUtil.getFieldByFieldName(countBoundSql, "additionalParameters");
			if (additionalParametersField != null) {
				Object mo =  ReflectUtil.getValueByFieldName(boundSql, "additionalParameters");
				ReflectUtil.setValueByFieldName(countBoundSql, "additionalParameters", mo);
			}

			//构建Mybatis的ParameterHandler用来设置总数sql参数
			ParameterHandler handler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql);
			//设置总数sql参数
			handler.setParameters(ps);
			//执行查询
			ResultSet rs = ps.executeQuery();
			while (rs.next()) {
				total = rs.getInt(1);
			}

		} finally {
		}

		return total;
	}

这个方法比较长,实际的基本思路是,获得原来的sql,拼接count sql,再用就的bound构造新的bound,再执行新的查询。之前一直无法使用foreach标签,关键点在于

//将原来的metaParameters复制到新的countBoundSql中
			Field metaParamsField = ReflectUtil.getFieldByFieldName(countBoundSql, "metaParameters");
			if (metaParamsField != null) {
				MetaObject mo = (MetaObject) ReflectUtil.getValueByFieldName(boundSql, "metaParameters");
				ReflectUtil.setValueByFieldName(countBoundSql, "metaParameters", mo);
			}

			//将原来的additionalParameters复制到新的countBoundSql中
			Field additionalParametersField = ReflectUtil.getFieldByFieldName(countBoundSql, "additionalParameters");
			if (additionalParametersField != null) {
				Object mo =  ReflectUtil.getValueByFieldName(boundSql, "additionalParameters");
				ReflectUtil.setValueByFieldName(countBoundSql, "additionalParameters", mo);
			}

这四行将构造新bound没带过来,但是执行这些绑定又必须的东西带过来了,然后,才匹配上了。最后我们看看getLimitedData

private Object getLimitedData(Invocation invocation, MetaObject metaStatementHandler, BoundSql boundSql, Map param) throws InvocationTargetException, IllegalAccessException {
		//获取当前需要执行的sql
		String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		//修改sql,这里使用的是mysql,如果是其他数据库则需要修改
		if(null != param.get(this.pageNo) && null != param.get(this.pageSize)){
		     sql = this.concatPageSql(sql, param);
        }
		//修改当前需要执行的sql
		metaStatementHandler.setValue("delegate.boundSql.sql", sql);
		//相当于调用StatementeHandler的prepare方法,预编译了当前sql,并设置原有的参数,但是少了两个分页参数,它返回的是一个PreparedStatement对象
		PreparedStatement ps = (PreparedStatement) invocation.proceed();
		return ps;

	}

其实很简单,就是改了下sql,然后执行查询返回。
  虽然现在遇见的问题都解决了,但是很可能我的实现还是有问题的,因为Interceptor提供要重写的方法不止这一个,而我还不知道其它的都干什么用,后面慢慢研究吧。

配置

插件写出来了,就要想想怎么生效

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:/mybatis/mybatis-config.xml" />
        <property name="dataSource" ref="dataSource" />
        <!-- 自动扫描mapping.xml文件 -->
        <property name="mapperLocations" value="classpath*:/net/dgg/tmd/foundation/platform/**/*Mapper.xml"></property>
        <!--配置分页插件-->
        <property name="plugins">
            <list>
                <bean class="net.dgg.tmd.foundation.platform.common.page.PageInterceptor" />
            </list>
        </property>
    </bean>

好了,打完收工,后面整理出来具体的例子再详细维护吧,这里已经基本说完了这个插件的关键实现了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

征途无悔

发文不易,谢谢认可

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值