[Java][MyBatis]物理分页实现

 [Java][MyBatis]物理分页实现

Mybatis3.0出来已有段时间了,其实自己挺喜欢这样的一个持久化框架的,因为它简单实用,学习成本低。Mybatis3.0在整体结构上和ibatis2.X差不多,改进特性如下:

1.        解析xml引进了Xpath,不像ibatis2.x那样业余

2.        动态sqlOGNL解析

3.        加入注解配置sql,感觉没什么特别大的用途,我更喜欢xml方式,代码和配置分离,这也是ibatis的初衷

4.        加强了缓存这块的功能。Mybatis3.0把缓存模块分得更细,分为“持久实现(prepetual)”和“资源回收策略实现(eviction)”,更好的对缓存功能进行自己组合和扩展

5.        终于加入的plugin功能,就像struts一样,这样就可以很好的扩展内部的Executor,StatementHandler….等内部对象功能。

 

 

MyBatis的分页功能还是基于内存分页(查找出所有记录再取出偏移量的记录,如果jdbc驱支持absolute定位或者rs.next()到指定偏移位置),其实这样的分页实现基本没用,特别是大量数据情况下。不过我们可以通过plugin功能来扩展MyBatis的分页功能、实现物理分页。具体做法如下:

1、编写分页插件类:

/**
 * 版权所有:华信软件
 * 项目名称:ACWS框架类
 * 创建者: Wangdf
 * 创建日期: 2014-4-2
 * 文件说明: ACWS框架分页接口类
 */
package framework.core.interceptor;

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

import org.apache.commons.collections.MapUtils;
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.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;

import framework.core.util.DBUtil;

/**
 * ACWS框架分页接口类
 * @author Wangdf
 *
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class PaginationInterceptor implements Interceptor {
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		MetaObject metaStatementHandler =  MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY);

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

        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        
    	String sql = boundSql.getSql();
		StringBuffer sbSql = new StringBuffer();

    	// 重写sql
		Configuration configuration = (Configuration)metaStatementHandler.getValue("delegate.configuration");
		DBUtil dbUtil = new DBUtil(configuration);
        if(dbUtil.isMySQL()){
        	sbSql.append(sql).append(" LIMIT ").append(rowBounds.getOffset()).append(", ").append(rowBounds.getLimit());
            metaStatementHandler.setValue("delegate.boundSql.sql", sbSql.toString());
            
            // 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数
            metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
            metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
		} else if(dbUtil.isOracle()){
			sbSql.append("SELECT * ");
			sbSql.append("  FROM (SELECT ROWNUM RN, NOPAGESQL.*   ");
			sbSql.append("          FROM (").append(sql).append(") NOPAGESQL ");
			sbSql.append("         WHERE ROWNUM <= ").append(rowBounds.getLimit()+rowBounds.getOffset()).append(")");
			sbSql.append(" WHERE RN >= ").append(rowBounds.getOffset());
	        metaStatementHandler.setValue("delegate.boundSql.sql", sbSql.toString());
	        
	        // 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数
	        metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
	        metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
		} else {
			
		}
        
        // 将执行权交给下一个拦截器
        return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
	}

	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
		
	}

}

 

2、配置读取类

/**
 * 版权所有:华信软件
 * 项目名称:ACWS框架类
 * 创建者: Wangdf
 * 创建日期: 2014-4-2
 * 文件说明: ACWS框架数据库相关工具类
 */
package framework.core.util;

import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ACWS框架数据库相关工具类
 * @author Wangdf
 */
public class DBUtil {
	private static final String DBTYPE_MYSQL = "MySQL";//支持的类型:MySQL,Oracle
	private static final String DBTYPE_ORACLE = "Oracle";//支持的类型:MySQL,Oracle

	private static Logger logger = LoggerFactory.getLogger(DBUtil.class);
	private Configuration configuration = null;
	
	private String dbType = "";
	private String defaultDateFormat = "";
	
	public DBUtil(Configuration configuration){
		if(configuration == null){
			logger.error("系统启动失败:MyBatis Configuration 对象为空!");
			throw new IllegalArgumentException("系统启动失败:MyBatis Configuration 对象为空!");
		}
		this.configuration = configuration;
		this.dbType = this.configuration.getVariables().getProperty("dbtype");
	    if(StringUtils.isBlank(dbType)){
	    	logger.error("数据库类型没有配置!");
	    } else {
	    	logger.info("数据库类型为:"+dbType);
	    }
		this.defaultDateFormat = this.configuration.getVariables().getProperty("defaultDateFormat");
	    if(StringUtils.isBlank(this.defaultDateFormat)){
	    	this.defaultDateFormat="yyyy-MM-dd";
	    	logger.info("数据库日期默认格式字符串没有指定!系统默认为:yyyy-MM-dd");
	    } else {
	    	logger.info("数据库日期默认格式字符串:"+this.defaultDateFormat);
	    }
	}
	
	/**
	 * 判断是否是Oracle数据库
	 * @return
	 * @author wangdf
	 */
	public boolean isOracle(){
		return DBTYPE_ORACLE.equals(this.dbType);
	}
	
	
	/**
	 * 判断是否是MySQL数据库
	 * @return
	 * @author wangdf
	 */
	public boolean isMySQL(){
		return DBTYPE_MYSQL.equals(this.dbType);
	}
	
	/**
	 * 取得数据库类型
	 * @return
	 * @author wangdf
	 */
	public String getDbType(){
		return this.dbType;
	}
	
	
	/**
	 * 取得默认日期格式
	 * @return
	 * @author wangdf
	 */
	public String getDefaultDateFormat(){
		return this.defaultDateFormat;
	}

}


 

3、在mybatis全局配置文件设置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	
	<properties>
	    <property name="dbtype" value="MySQL"/><!-- 数据库类型:MySQL、Oracle -->
	    <property name="defaultDateFormat" value="yyyy-MM-dd"/>
	</properties>

	<settings>
		<setting name="cacheEnabled" value="true" />
		<setting name="lazyLoadingEnabled" value="true" />
		<setting name="multipleResultSetsEnabled" value="true" />
		<setting name="useColumnLabel" value="true" />
		<setting name="defaultExecutorType" value="REUSE" />
		<setting name="defaultStatementTimeout" value="25000" />
	</settings>
	
	<plugins>
		<plugin interceptor="framework.core.interceptor.PaginationInterceptor"></plugin>
	</plugins>
</configuration>


 


 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值