MyBatis分页插件实现

Posted claindoc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis分页插件实现相关的知识,希望对你有一定的参考价值。

日常开发中,MyBatis已经成为数据持久层实现的重要角色,以下就是一个使用MyBatis开发的一个分页插件的实现。关于Mybatis的插件概念可以查看MyBatis官网

技术分享图片

查看官网教程可以得知,MyBatis允许客户对以下类的方法进行拦截。

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  public void setProperties(Properties properties) {
  }
}

<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

对官网教程的解读:

  1. 用户自定义拦截器类需要实现Interceptor接口。
  2. 指定拦截器需要拦截的方法。ExamplePlugin是对Executor的update方法进行拦截。查看Executor源码如下:
    int update(MappedStatement ms, Object parameter) throws SQLException;
  3. 配置文件

以下为分页插件的实现,是对Executor
Java <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;方法的拦截。

1.定义拦截器类,实现Interceptor接口,指定拦截的方法。

@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) })
public class PageInterceptor implements Interceptor {
    static int MAPPED_STATEMENT_INDEX = 0;
    static int PARAMETER_INDEX = 1;
    static int ROWBOUNDS_INDEX = 2;
    static int RESULT_HANDLER_INDEX = 3;

    Dialect dialect;

    public Object intercept(Invocation invocation) throws Throwable {
        processIntercept(invocation.getArgs());
        return invocation.proceed();
    }

    void processIntercept(final Object[] queryArgs) {
        MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];
        Object parameter = queryArgs[PARAMETER_INDEX];
        // 获取分页信息RowBounds
        final RowBounds rowBounds = (RowBounds) queryArgs[ROWBOUNDS_INDEX];
        int pageCurrent = rowBounds.getOffset();
        int pageSize = rowBounds.getLimit();

        // 获取NAMESPACE和方法
        String[] nameSpaceId = ms.getId().split("\\.");
        if (!ArrayUtils.isEmpty(nameSpaceId) && nameSpaceId[nameSpaceId.length - 1].startsWith("query")) {
            if (dialect.supportsLimit() && (pageCurrent != RowBounds.NO_ROW_OFFSET || pageSize != RowBounds.NO_ROW_LIMIT)) {
                BoundSql boundSql = ms.getBoundSql(parameter);
                String sql = boundSql.getSql().trim();
                if (dialect.supportsLimitOffset()) {
                    sql = dialect.getPageSql(sql, pageCurrent, pageSize);
                    pageCurrent = RowBounds.NO_ROW_OFFSET;
                } else {
                    sql = dialect.getPageSql(sql, 0, pageSize);
                }
                pageSize = RowBounds.NO_ROW_LIMIT;

                queryArgs[ROWBOUNDS_INDEX] = new RowBounds(pageCurrent, pageSize);
                BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject());
                MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
                
                //需要将metaParameters赋值过去..  
                MetaObject countBsObject = SystemMetaObject.forObject(newBoundSql);  
                MetaObject boundSqlObject = SystemMetaObject.forObject(boundSql);  
                countBsObject.setValue("metaParameters",boundSqlObject.getValue("metaParameters"));  
                
                queryArgs[MAPPED_STATEMENT_INDEX] = newMs;
            }
        }
    }

    /** @see MapperBuilderAssistant */
    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());

        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        builder.keyProperty(ms.getKeyProperties() == null ? null : ms.getKeyProperties()[0]);

        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());

        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());

        return builder.build();
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
        try { 
            String dialectClassValue  =properties.getProperty("dialectClass");
            if("com.page.dialect.mysqlDialect".equals(dialectClassValue)){
                dialect = (Dialect) Class.forName(MySqlDialect.class.getCanonicalName()).newInstance();
            }else{
                dialect = (Dialect) Class.forName(OracleDialect.class.getCanonicalName()).newInstance();
            }
        } catch (Exception e) {
            throw new RuntimeException("cannot create dialect instance by dialectClass : " + OracleDialect.class.getCanonicalName(), e);
        }
    }

    public static class BoundSqlSqlSource implements SqlSource {
        BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}

/**
 * 类似HIBERNATE的Dialect,但只精简出分页部分
 */
public class Dialect {

    /**
     * 将SQL变成分页SQL语句</br> 
     * 拆分原始SQL,拼装需要的分页SQL.
     */
    public String getPageSql(String sql, int pageCurrent, int pageSize) {
        throw new UnsupportedOperationException("paged queries not supported");
    }

    public boolean supportsLimit() {
        return false;
    }

    public boolean supportsLimitOffset() {
        return supportsLimit();
    }
}

public class MySqlDialect extends Dialect {

    public String getPageSql(String sql, int pageCurrent, int pageSize) {
        // 原始SQL
        String orgSql = sql.trim();
        // 重置当前页
        pageCurrent = pageCurrent - 1;
        //起始数据行
        int offset=pageCurrent*pageSize;
        // 最终SQL
        StringBuffer finaleSql = new StringBuffer();
        
        finaleSql.append("select * from ( ");
        

        // 追加原始SQL
        finaleSql.append(orgSql);

        //用limit分页
        finaleSql.append(" limit "+String.valueOf(offset)+","+String.valueOf(pageSize));
         
        
        finaleSql.append("  ) as tabletotal");
        
    
        return finaleSql.toString();
    }

    public boolean supportsLimit() {
        return true;
    }

    public boolean supportsLimitOffset() {
        return true;
    }
}


public class OracleDialect extends Dialect {

    public String getPageSql(String sql, int pageCurrent, int pageSize) {
        // 原始SQL
        String orgSql = sql.trim();
        // 重置当前页
        pageCurrent = pageCurrent - 1;

        // 最终SQL
        StringBuffer finaleSql = new StringBuffer();
        // 如果当前页>0
        finaleSql.append("select * from (select * from ( select row_.*, rownum rownum_ from ( ");

        // 追加原始SQL
        finaleSql.append(orgSql);

        // 如果当前页>0
        if (pageCurrent > 0) {
            finaleSql.append(" ) row_ )) where rownum_ <= " + ((pageCurrent * pageSize) + "+" + pageSize) + " and rownum_ > "
                    + String.valueOf(pageCurrent * pageSize));
        } else {// 如果当前页<=0
            finaleSql.append(" ) row_ )) where rownum_ <= " + String.valueOf(pageSize));
        }

        return finaleSql.toString();
    }

    public boolean supportsLimit() {
        return true;
    }

    public boolean supportsLimitOffset() {
        return true;
    }
}



public class SystemMetaObject {
    public static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    public static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    public static final MetaObject NULL_META_OBJECT = MetaObject.forObject(NullObject.class, DEFAULT_OBJECT_FACTORY,
            DEFAULT_OBJECT_WRAPPER_FACTORY);

    private SystemMetaObject() {
        // Prevent Instantiation of Static Class
    }

    private static class NullObject {
    }

    public static MetaObject forObject(Object object) {
        return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
    }
}
<configuration>
    <properties>
        <property name="dialect" value="oracle" />
    </properties>
    <settings>
        <setting name="lazyLoadingEnabled" value="true" />
        <setting name="jdbcTypeForNull" value="VARCHAR" />
    </settings>
    <plugins>
        <plugin interceptor="com.page.interceptor.PageInterceptor">
            <property name="dialectClass" value="com..page.dialect.OracleDialect" />
        </plugin>
    </plugins>
</configuration>


以上是关于MyBatis分页插件实现的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis分页插件实现

springboot使用Mybatis分页插件

Mybatis分页插件PageHelper实现分页查询

七:MyBatis逆向工程&分页插件

七:MyBatis逆向工程&分页插件

四步教你SpringBoot+Mybatis-plus分页插件(简单实现)