/* * @(#)PaginationInterceptor.java 2012-8-17 下午2:32:29 * * Copyright (c) 2011-2012 Makersoft.org all rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * */ package org.makersoft.shards.plugin; import java.util.Properties; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.MappedStatement.Builder; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.SqlSource; 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.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.makersoft.shards.MyBatisShardsException; import org.makersoft.shards.plugin.spi.Dialect; /** * 物理真分页组件. * <p> * 使用方法: * </p> * * <code> * <plugins> * <plugin interceptor="org.makersoft.shards.plugin.PaginationInterceptor"> * <property name="dialect" value="org.makersoft.shards.plugin.spi.impl.MySQLDialect"/> * </plugin> * </plugins> * </code> * * @version 2012-8-17 下午2:32:29 * @author Feng Kuok */ @Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) }) public class PaginationInterceptor implements Interceptor { static int INDEX_MAPPED_STATEMENT = 0; static int INDEX_PARAMETER = 1; static int INDEX_ROW_BOUNDS = 2; static int INDEX_RESULT_HANDLER = 3; protected Dialect dialect; @Override public Object intercept(Invocation invocation) throws Throwable { final MappedStatement mappedStatement = this.getMappedStatement(invocation); final Object parameter = this.getParameter(invocation); final RowBounds rowBounds = this.getRowBounds(invocation); final int offset = rowBounds.getOffset(); final int limit = rowBounds.getLimit(); if (dialect.supportLimit() && (offset != RowBounds.NO_ROW_OFFSET || limit != RowBounds.NO_ROW_LIMIT)) { BoundSql boundSql = mappedStatement.getBoundSql(parameter); String sql = boundSql.getSql().trim(); if (dialect.supportOffsetLimit()) { sql = dialect.getLimitString(sql, offset, limit); } else { sql = dialect.getLimitString(sql, RowBounds.NO_ROW_OFFSET, limit); } this.setMappedStatement(invocation, this.buildMappedStatement(mappedStatement, boundSql, sql)); this.setRowBounds(invocation, RowBounds.DEFAULT); } return invocation.proceed(); } private MappedStatement buildMappedStatement(MappedStatement ms, BoundSql boundSql, String sql) { Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), new BoundSqlSqlSource(ms, boundSql, sql), ms.getSqlCommandType()); builder.resource(ms.getResource()); builder.parameterMap(ms.getParameterMap()); builder.resultMaps(ms.getResultMaps()); builder.fetchSize(ms.getFetchSize()); builder.timeout(ms.getTimeout()); builder.statementType(ms.getStatementType()); builder.resultSetType(ms.getResultSetType()); builder.cache(ms.getCache()); builder.flushCacheRequired(ms.isFlushCacheRequired()); builder.useCache(ms.isUseCache()); builder.keyGenerator(ms.getKeyGenerator()); builder.keyProperty(delimitedArraytoString(ms.getKeyProperties())); builder.keyColumn(delimitedArraytoString(ms.getKeyColumns())); builder.databaseId(ms.getDatabaseId()); return builder.build(); } private MappedStatement getMappedStatement(Invocation invocation) { return (MappedStatement) invocation.getArgs()[INDEX_MAPPED_STATEMENT]; } private void setMappedStatement(Invocation invocation, MappedStatement mappedStatement) { invocation.getArgs()[INDEX_MAPPED_STATEMENT] = mappedStatement; } private Object getParameter(Invocation invocation) { return invocation.getArgs()[INDEX_PARAMETER]; } private RowBounds getRowBounds(Invocation invocation) { return (RowBounds) invocation.getArgs()[INDEX_ROW_BOUNDS]; } private void setRowBounds(Invocation invocation, RowBounds rowBounds) { invocation.getArgs()[INDEX_ROW_BOUNDS] = rowBounds; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { String dialectClass = properties.getProperty("dialect"); try { dialect = (Dialect) Class.forName(dialectClass).newInstance(); } catch (Exception e) { throw new MyBatisShardsException("Can not create dialect instance by dialect:" + dialectClass, e); } } private static String delimitedArraytoString(String[] in) { if (in == null || in.length == 0) { return null; } else { StringBuffer answer = new StringBuffer(); for (String str : in) { answer.append(str).append(","); } return answer.toString(); } } public static class BoundSqlSqlSource implements SqlSource { private final BoundSql boundSql; public BoundSqlSqlSource(MappedStatement ms, BoundSql boundSql, String sql) { this.boundSql = buildBoundSql(ms, boundSql, sql); } public BoundSql getBoundSql(Object parameterObject) { return boundSql; } private BoundSql buildBoundSql(MappedStatement ms, BoundSql boundSql, String sql) { BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject()); for (ParameterMapping mapping : boundSql.getParameterMappings()) { String prop = mapping.getProperty(); if (boundSql.hasAdditionalParameter(prop)) { newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop)); } } return newBoundSql; } } }