簡單實現ibatis的物理分頁

本文是原創文章,轉載請註明出處:http://my.oschina.net/fqt/blog/52431java

一直以來ibatis的分頁都是經過滾動ResultSet實現的,應該算是邏輯分頁吧。邏輯分頁雖然能很乾淨地獨立於特定數據庫,但效率在多數狀況下不及特定數據庫支持的物理分頁,而hibernate的分頁則是直接組裝sql,充分利用了特定數據庫的分頁機制,效率相對較高。sql

網上已有《使ibatis支持hibernate式的物理分頁》等相似的文章以繼承SqlExecutor的方式實現了物理分頁,可是侵入性很是強,還得實現數據庫方言,方法很是複雜。同時SqlExecutor不是接口,對它的方法繼承也不能保證版本穩定。本文中將介紹的方式是實現queryWithSqlHandler方法,在查詢前將經過SqlHandler接口把sql傳給調用者,再用處理後的sql進行最終查詢,從而實現物理分頁等功能:數據庫

//用queryWithSqlHandler方法實現物理分頁的例子:
public Page queryPage(String statementId, param, final Page page){
	final int pageNum = page.getPageNum();
	final int pageSize = page.getPageSize();
	List list = queryWithSqlHandler(statementId, param, new SqlHandler() {
					@Override
					public String handle(String sql, Object[] params) throws SQLException {
						//查詢總記錄數
						int total = getJdbcTemplate().queryForInt("select count(1) as RECORDS from (" + sql + ")", params);
						page.setTotal(total);
						//返回通過分頁包裝後的Sql
						return "select * from (select row_.*, rownum row_num_ from ("+sql+") row_ where rownum<="+ pageNum*pageSize +") where row_num_ > "+ (pageNum-1)*pageSize;
					}
				});
	page.setRows(list);
	return page;
}

咱們來看queryWithSqlHandler方法的實現:app

/**
 * 提供查詢前對sql處理的功能
 *
 * @param statementId
 * @param param
 * @param sqlHandler sql處理器
 * @return
 */
private List queryWithSqlHandler(final String statementId, final Object param, final SqlHandler sqlHandler) {
	final SqlMapClientImpl smc = getSqlMapClient();
	if (sqlHandler != null) {
		final MappedStatement mappedStatement = smc.getMappedStatement(statementId);
		final Sql dySql = mappedStatement.getSql();
		if (Proxy.isProxyClass(dySql.getClass())) {
			log.debug("該Sql對象已是代理對象,設置新的sql處理器。");
			((SqlProxyHandler) Proxy.getInvocationHandler(dySql)).setSqlHandler(sqlHandler);
		} else {
			log.debug("建立Sql的代理對象!");
			final SqlProxyHandler sqlProxyHandler = new SqlProxyHandler(dySql, sqlHandler);
			final Class sqlClass = dySql.getClass();
			final Sql proxy = (Sql) Proxy.newProxyInstance(sqlClass.getClassLoader(), sqlClass.getInterfaces(), sqlProxyHandler);
			mappedStatement.setSql(proxy);
		}
	}
	try {
		return smc.queryForList(statementId, param);
	} catch (SQLException ex) {
		throw new RuntimeException("查詢失敗", ex);
	}
}
private static final class SqlProxyHandler implements InvocationHandler {

	private final Sql sql;
	private final ThreadLocal<SqlHandler> sqlHandler = new ThreadLocal();

	public SqlProxyHandler(Sql sql, SqlHandler handler) {
		this.sql = sql;
		setSqlHandler(handler);
	}

	public Sql getSql() {
		return sql;
	}

	public void setSqlHandler(SqlHandler handler) {
		this.sqlHandler.set(handler);
	}

	public SqlHandler getSqlHandler() {
		return sqlHandler.get();
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result = method.invoke(getSql(), args);
		if ("getSql".equals(method.getName()) && getSqlHandler() != null) {
			log.debug("原SQL: " + result);
			final StatementScope statementScope = (StatementScope) args[0];
			final Object[] params = statementScope.getParameterMap().getParameterObjectValues(statementScope, args[1]);
			result = getSqlHandler().handle((String) result, params);
			log.debug("處理後: " + result);
			setSqlHandler(null);//執行完成後清除線程局部變量,下次調用須要設置新值,不然不攔截getSql方法
		}
		return result;
	}
}
interface SqlHandler {

	/**
	 * 處理sql語句
	 *
	 * @param sql ibatis生成的sql語句,其中參數用?號佔位
	 * @param params sql對應的參數
	 * @return
	 * @throws Throwable
	 */
	String handle(String sql, Object[] params) throws Throwable;
}
相關文章
相關標籤/搜索