mybatis3.1分页自动添加总数
问题
1.mybatis默认分页是内存分页的,谁用谁崩溃啊!
类似于下面的DAO签名方法,只要有RowBounds入参,Mybatis即会自动内存分页:
@Select("SELECT * FROM cartan_common.t_app s WHERE s.author = #{param.author}") ArrayList<App> queryList(@Param("param")AppQueryParam appQueryParam,RowBounds rowBounds);
package com.ridge.dao.mybatis;import org.apache.commons.lang.reflect.FieldUtils;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.ibatis.executor.statement.PreparedStatementHandler;import org.apache.ibatis.executor.statement.RoutingStatementHandler;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.ParameterMapping;import org.apache.ibatis.plugin.*;import org.apache.ibatis.session.Configuration;import org.apache.ibatis.session.RowBounds;import org.hibernate.dialect.Dialect;import java.sql.Connection;import java.util.Properties;import java.util.regex.Matcher;import java.util.regex.Pattern;/** * 默认情况下 * 数据库的类型 * * @author : chenxh * @date: 13-7-5 */@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})public class PaginationInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { ... }}
getLimitString(originalSql, rowBounds.getOffset(), rowBounds.getLimit())
package com.ridge.dao.mybatis;import org.apache.commons.lang.reflect.FieldUtils;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.ibatis.executor.statement.PreparedStatementHandler;import org.apache.ibatis.executor.statement.RoutingStatementHandler;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.ParameterMapping;import org.apache.ibatis.plugin.*;import org.apache.ibatis.session.Configuration;import org.apache.ibatis.session.RowBounds;import org.hibernate.dialect.Dialect;import java.sql.Connection;import java.util.Properties;import java.util.regex.Matcher;import java.util.regex.Pattern;/** * 默认情况下 * 数据库的类型 * * @author : chenxh * @date: 13-7-5 */@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})public class PaginationInterceptor implements Interceptor { private final static Log logger = LogFactory.getLog(PaginationInterceptor.class); public static final String CONFIGURATION = "configuration"; private static Dialect dialect = null; private static final String ROW_BOUNDS = "rowBounds"; private static final String BOUND_SQL = "boundSql"; private static final String DIALECT = "dialect"; private static final String SQL = "sql"; private static final String OFFSET = "offset"; private static final String LIMIT = "limit"; public static final String DELEGATE = "delegate"; private static final int CONNECTION_INDEX = 0; private static final String INTERCEPTOR_CONF = "<plugins>\n" + "<plugin interceptor="" + PaginationInterceptor.class.getCanonicalName() + "">\n" + "<property name="dialect" value="" + DialetHelper.getSupportDatabaseTypes() + ""/>\n" + "</plugin>\n" + "</plugins>"; @Override public Object intercept(Invocation invocation) throws Throwable { RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget(); PreparedStatementHandler preparedStatHandler = (PreparedStatementHandler) FieldUtils.readField(statementHandler, DELEGATE, true); final Object[] queryArgs = invocation.getArgs(); Connection connection = (Connection) queryArgs[CONNECTION_INDEX]; RowBounds rowBounds = (RowBounds) FieldUtils.readField(preparedStatHandler, ROW_BOUNDS, true); BoundSql boundSql = (BoundSql) FieldUtils.readField(preparedStatHandler, BOUND_SQL, true); Configuration configuration = (Configuration) FieldUtils.readField(preparedStatHandler, CONFIGURATION, true); //没有分页,直接返回原调用 if (rowBounds == null || rowBounds == RowBounds.DEFAULT) { return invocation.proceed(); } //有分页 String originalSql = boundSql.getSql(); //1.获取总行数,将行数绑定到当前线程中 String countSql = MybatisHepler.getCountSql(originalSql); CountHelper.getCount(countSql, preparedStatHandler, configuration, boundSql, connection); //2.获取分页的结果集 ////////////////////////////////////////////////////////////////////////////////////////////// String pagingSql = dialect.getLimitString(originalSql, rowBounds.getOffset(), rowBounds.getLimit()); FieldUtils.writeField(boundSql, SQL, pagingSql, true); int size = 0; size = getPageParamNum(originalSql, pagingSql); if (size == 1) { ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, LIMIT, Integer.class); boundSql.getParameterMappings().add(builder.build()); boundSql.setAdditionalParameter(LIMIT, rowBounds.getLimit()); } if (size == 2) { ParameterMapping.Builder builder = new ParameterMapping.Builder( configuration, OFFSET, Integer.class); boundSql.getParameterMappings().add(builder.build()); boundSql.setAdditionalParameter(OFFSET, rowBounds.getOffset()); builder = new ParameterMapping.Builder(configuration, LIMIT, Integer.class); boundSql.getParameterMappings().add(builder.build()); boundSql.setAdditionalParameter(LIMIT, rowBounds.getLimit()); } FieldUtils.writeField(rowBounds, OFFSET, RowBounds.NO_ROW_OFFSET, true); FieldUtils.writeField(rowBounds, LIMIT, RowBounds.NO_ROW_LIMIT, true); if (logger.isDebugEnabled()) { logger.debug("\n" + originalSql + "\n对应的分页SQL:\n" + boundSql.getSql() + "\n对应的count SQL:\n" + countSql); } return invocation.proceed(); } /** * 获取用于控制分页的问号的个数 * * @param originalSql * @param pagingSql * @return */ private int getPageParamNum(String originalSql, String pagingSql) { int size = 0; String addSql = pagingSql.replace(originalSql, ""); Pattern pattern = Pattern.compile("[?]"); Matcher matcher = pattern.matcher(addSql); while (matcher.find()) { size++; } return size; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { if (PaginationInterceptor.dialect == null) { String dialect = properties.getProperty(DIALECT); if (dialect == null) { throw new RuntimeException("拦截器未提供dialect的配置,正确配置参见:\n" + INTERCEPTOR_CONF); } PaginationInterceptor.dialect = DialetHelper.getDialect(dialect); } }}
public Page<App> queryAppList(AppQueryParam queryParam,Paging paging){ List<App> apps = appMapper.queryList(queryParam, MybatisHepler.toRowBounds(paging)); Page<App> appPage = new Page<App>(); appPage.setResult(apps); appPage.setPageSize(paging.getPageSize()); return appPage; }
package com.ridge.dao.mybatis;import org.apache.commons.lang.reflect.FieldUtils;import org.apache.ibatis.executor.statement.PreparedStatementHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;import org.apache.ibatis.session.Configuration;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;/** * @author : chenxh * @date: 13-7-8 */public class CountHelper { private static final String MAPPED_STATEMENT = "mappedStatement"; private static Logger logger = LoggerFactory.getLogger(CountHelper.class); /** * 保存计算总行数的值 */ private static ThreadLocal<Integer> totalRowCountHolder = new ThreadLocal<Integer>(); /** * 获取查询对象的总行数 * @param sql 获取总行数的SQL * @param statementHandler * @param configuration * @param boundSql * @param connection * @throws Throwable */ static void getCount(String sql, PreparedStatementHandler statementHandler, Configuration configuration, BoundSql boundSql, Connection connection) throws Throwable{ Object parameterObject = statementHandler.getParameterHandler().getParameterObject(); if (logger.isDebugEnabled()) { logger.debug("Total count SQL [{}] ", sql); logger.debug("Total count Parameters: {} ", parameterObject); } PreparedStatement countStmt = null; ResultSet rs = null; try { countStmt = connection.prepareStatement(sql); final BoundSql countBS = new BoundSql(configuration, sql, boundSql.getParameterMappings(), parameterObject); MappedStatement mappedStatement = (MappedStatement)FieldUtils.readField(statementHandler, MAPPED_STATEMENT, true); DefaultParameterHandler handler = new DefaultParameterHandler(mappedStatement, parameterObject, countBS); handler.setParameters(countStmt); rs = countStmt.executeQuery(); int count = 0; if (rs.next()) { count = rs.getInt(1); } if (logger.isDebugEnabled()) { logger.debug("Total count: {}", count); } totalRowCountHolder.set(count); } finally { try { if (rs != null) { rs.close(); } } finally { if (countStmt != null) { countStmt.close(); } } } } /** * 获取当前线程对应的分页查询的总行数 * * @return */ public static int getTotalRowCount() { return totalRowCountHolder.get(); }}
public Page<App> queryAppList(AppQueryParam queryParam,Paging paging){ List<App> apps = appMapper.queryList(queryParam, MybatisHepler.toRowBounds(paging)); Page<App> appPage = new Page<App>(); appPage.setResult(apps); appPage.setPageSize(paging.getPageSize()); appPage.setTotalRows(CountHelper.getTotalRowCount());//①注意这里!! return appPage; }
package com.ridge.dao.mybatis;import com.ridge.dao.Page;/** * @author : chenxh * @date: 13-7-8 */public class TotalRowValueMount { public void setTotalRows(Page page){ page.setTotalRows(CountHelper.getTotalRowCount()); }}
<!-- 所有分页查询的方法自动设置总行数 --> <aop:config> <aop:aspect id="pagingQueryAspect" ref="totalRowCounter"> <aop:pointcut id="pagingQueryMethods" expression="execution(public com.ridge.dao.Page *(..))"/> <aop:after-returning pointcut-ref="pagingQueryMethods" returning="page" method="setTotalRows"/> </aop:aspect> </aop:config> <bean id="totalRowCounter" name="code"> <?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> <settings> <!-- changes from the defaults --> <setting name="lazyLoadingEnabled" value="false" /> </settings> <plugins> <plugin interceptor="com.ridge.dao.mybatis.PaginationInterceptor"> <property name="dialect" value="MYSQL"/> </plugin> </plugins> <mappers> <package name="com.xxx.yyy"/> </mappers></configuration>