有了这个插件设计,就不用在那么麻烦的按照老的Ibatis的方式设计分页了,只需按照MyBatis要求的插件方式来拦截sql修改成分页形式了
先弄个基类,共享代码,支持所有的数据库,将不同的东西分离出来
Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public abstract class PaginationBasePlugin implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { Object[] queryArgs = invocation.getArgs(); MappedStatement ms = (MappedStatement) queryArgs[0]; BoundSql boundSql = ms.getBoundSql(queryArgs[1]); String sql = boundSql.getSql().trim(); Object args = queryArgs[1]; sql = paginationSql(sql,args); //通过子类实现 BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(), boundSql.getParameterObject()); MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql)); queryArgs[0] = newMs; return invocation.proceed(); } protected abstract String paginationSql(String sql,Object args); //子类实现 //see: MapperBuilderAssistant private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) { MappedStatement.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.getKeyProperty()); //setStatementTimeout() builder.timeout(ms.getTimeout()); //setStatementResultMap() builder.parameterMap(ms.getParameterMap()); //setStatementResultMap() builder.resultMaps(ms.getResultMaps()); builder.resultSetType(ms.getResultSetType()); //setStatementCache() builder.cache(ms.getCache()); builder.flushCacheRequired(ms.isFlushCacheRequired()); builder.useCache(ms.isUseCache()); return builder.build(); } public static class BoundSqlSqlSource implements SqlSource { private BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } public Object plugin(Object target) { return Plugin.wrap(target, this); } public void setProperties(Properties properties) { //suffix = properties.get("dirname").toString(); } }
以MySql数据库分页举例,做一个MySqlPaginationPlugin
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class MySqlPaginationPlugin extends PaginationBasePlugin { @Override protected String paginationSql(String sql,Object args) { if(args instanceof Map){ if(((Map) args).containsKey("pagination")){ Map map = (Map) args; RowBounds rowBounds = (RowBounds) map.get("pagination"); sql = sql+" limit " + rowBounds.getOffset()+ ","+rowBounds.getLimit(); //分页了 } } return sql; } }
MySql的分页比较简单,其他数据库的分页要求对sql参数进行分离和复杂点的修改,这些玩意大家自己就会倒腾了,网上有很多代码,再次不弄了
用法如下,从代码中可以看出,只要Xml Mapper的Sql语句输入参数为map类型,并且里面含有pagination键值就可以了,否则不分页
class ClientGateway { static forceAsListOps = ["selectClient"]; def queryClient(RowBounds rowBounds) { HashMap map = new HashMap() map.put("pagination", rowBounds); //关键标志 sqlMapper.selectList("selectClient", map); } }
pagination的值为一个RowBounds类型,这个是MyBatis里面带的东西,我就直接拿来用了,反正就那两个属性offset,limit
这样同一个sql语句既可以支持分页也可以不支持分页,避免了写两套相同的sql,不好维护
那么其中这个sql语句的count又如何不写两套sql弄出来呢?
同样道理,自己在map里面再加个特殊的标记,就可以一个sql语句也支持count了
综上,通过MyBatis的插件拦截sql是一个sql语句既可以直接执行,也可以支持分页和获取所有行数。提高了可维护性。
代码中的pagination字符串自己弄成常量吧,不满意的地方可以自己修改,思路就是这样了,我使着挺好用。