MyBatis 物理分页foreach 参数失效
MyBatis-3.4.4.jar使用分页插件时并且查询条件包含foreach时,分页插件在执行count语句时会抛出异常,报参数为空异常。分页插件会新增一个COUNT的SQL,并复制原BoundSql对象,然后使用DefaultParameterHandler.setParameters给COUNT语句设值。foreach过程中产生的参数变量是放在AdditionalParameter中,但复制BoundSql时并没有复制其中的additionalParameters字段,而由foreach产生的参数是存放在additionalParameters中,所以导致参数空异常。解决方案就是反射获取到additionalParameters字段的值并设置到新产生的BoundSql中去。
// 只拦截select部分 @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class PaginationInterceptor implements Interceptor { private Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class); Dialect dialect = new MySql5Dialect(); public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; Object parameter = invocation.getArgs()[1]; BoundSql boundSql = mappedStatement.getBoundSql(parameter); String originalSql = boundSql.getSql().trim(); RowBounds rowBounds = (RowBounds) invocation.getArgs()[2]; Object parameterObject = boundSql.getParameterObject(); if (boundSql == null || boundSql.getSql() == null || "".equals(boundSql.getSql())) return null; // 分页参数--上下文传参 PageInfo page = null; // map传参每次都将currentPage重置,先判读map再判断context if (parameterObject instanceof PageInfo) { page = (PageInfo) parameterObject; } else if (parameterObject instanceof Map) { Map<String, Object> map = (Map<String, Object>) parameterObject; if (map.containsKey("page")) { page = (PageInfo) map.get("page"); } } else if (null != parameterObject) { Field pageField = ReflectionUtil.getFieldByFieldName(parameterObject, "page"); if (pageField != null) { page = (PageInfo) ReflectionUtil.getValueByFieldName(parameterObject, "page"); } } // 后面用到了context的东东 if (page != null && page.isPagination() == true) { if (page.getPageSize() > 10000) { // throw new Exception("pageSize不能大于10000"); logger.warn("pageSize建议不要大于10000,当前sqlId:{},page:{}", mappedStatement.getId(), JSON.toJSONString(page)); } int totalRows = page.getTotalRows(); // 得到总记录数 if (totalRows == 0 && page.isNeedCount()) { StringBuffer countSql = new StringBuffer(); countSql.append(MySql5PageHepler.getCountString(originalSql)); Connection connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection(); PreparedStatement countStmt = connection.prepareStatement(countSql.toString()); BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql.toString(), boundSql.getParameterMappings(), parameterObject); Field metaParamsField = ReflectionUtil.getFieldByFieldName(boundSql, "metaParameters"); if (metaParamsField != null) { MetaObject mo = (MetaObject) ReflectionUtil.getValueByFieldName(boundSql, "metaParameters"); ReflectionUtil.setValueByFieldName(countBS, "metaParameters", mo); } Field additionalField = ReflectionUtil.getFieldByFieldName(boundSql, "additionalParameters"); if (additionalField != null) { Map<String, Object> map = (Map<String, Object>) ReflectionUtil.getValueByFieldName(boundSql, "additionalParameters"); ReflectionUtil.setValueByFieldName(countBS, "additionalParameters", map); } setParameters(countStmt, mappedStatement, countBS, parameterObject); ResultSet rs = countStmt.executeQuery(); if (rs.next()) { totalRows = rs.getInt(1); } rs.close(); countStmt.close(); connection.close(); } // 分页计算 page.init(totalRows, page.getPageSize(), page.getCurrentPage()); if (rowBounds == null || rowBounds == RowBounds.DEFAULT) { rowBounds = new RowBounds(page.getPageSize() * (page.getCurrentPage() - 1), page.getPageSize()); } // 分页查询 本地化对象 修改数据库注意修改实现 String pagesql = dialect.getLimitString(originalSql, rowBounds.getOffset(), rowBounds.getLimit()); invocation.getArgs()[2] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT); BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), pagesql, boundSql.getParameterMappings(), boundSql.getParameterObject()); Field metaParamsField = ReflectionUtil.getFieldByFieldName(boundSql, "metaParameters"); if (metaParamsField != null) { MetaObject mo = (MetaObject) ReflectionUtil.getValueByFieldName(boundSql, "metaParameters"); ReflectionUtil.setValueByFieldName(newBoundSql, "metaParameters", mo); } Field additionalField = ReflectionUtil.getFieldByFieldName(boundSql, "additionalParameters"); if (additionalField != null) { Map<String, Object> map = (Map<String, Object>) ReflectionUtil.getValueByFieldName(boundSql, "additionalParameters"); ReflectionUtil.setValueByFieldName(newBoundSql, "additionalParameters", map); } MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql)); invocation.getArgs()[0] = newMs; } return invocation.proceed(); } public static class BoundSqlSqlSource implements SqlSource { BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } public Object plugin(Object arg0) { return Plugin.wrap(arg0, this); } public void setProperties(Properties arg0) { } /** * 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.DefaultParameterHandler * * @param ps * @param mappedStatement * @param boundSql * @param parameterObject * @throws SQLException */ private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { Configuration configuration = mappedStatement.getConfiguration(); TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject); for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); PropertyTokenizer prop = new PropertyTokenizer(propertyName); if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) && boundSql.hasAdditionalParameter(prop.getName())) { value = boundSql.getAdditionalParameter(prop.getName()); if (value != null) { value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length())); } } else { value = metaObject == null ? null : metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); if (typeHandler == null) { throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName + " of statement " + mappedStatement.getId()); } typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType()); } } } } 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(buildKeyProperty(ms.getKeyProperties())); builder.timeout(ms.getTimeout()); builder.parameterMap(ms.getParameterMap()); builder.resultMaps(ms.getResultMaps()); builder.useCache(ms.isUseCache()); builder.cache(ms.getCache()); MappedStatement newMs = builder.build(); return newMs; } private static String buildKeyProperty(String[] props) { if (null != props && props.length > 0) { StringBuffer sb = new StringBuffer(); for (String p : props) { sb.append(p).append(","); } return sb.substring(0, sb.length() - 1); } return null; } }
下面是:ReflectUtil
public class ReflectionUtil { private static final Log logger = LogFactory.getLog(ReflectionUtil.class); public static void setFieldValue(Object object, String fieldName, Object value) { Field field = getDeclaredField(object, fieldName); if (field == null) { throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]"); } makeAccessible(field); try { field.set(object, value); } catch (IllegalAccessException e) { } } public static Object getFieldValue(Object object, String fieldName) { Field field = getDeclaredField(object, fieldName); if (field == null) { throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]"); } makeAccessible(field); Object result = null; try { result = field.get(object); } catch (IllegalAccessException e) { } return result; } public static Object invokeMethod(Object object, String methodName, Class<?>[] parameterTypes, Object[] parameters) throws InvocationTargetException { Method method = getDeclaredMethod(object, methodName, parameterTypes); if (method == null) { throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + object + "]"); } method.setAccessible(true); try { return method.invoke(object, parameters); } catch (IllegalAccessException e) { } return null; } protected static Field getDeclaredField(Object object, String fieldName) { for (Class superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { try { return superClass.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { } } return null; } protected static void makeAccessible(Field field) { if ((!Modifier.isPublic(field.getModifiers())) || (!Modifier.isPublic(field.getDeclaringClass().getModifiers()))) { field.setAccessible(true); } } protected static Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes) { for (Class superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { try { return superClass.getDeclaredMethod(methodName, parameterTypes); } catch (NoSuchMethodException e) { } } return null; } public static <T> Class<T> getSuperClassGenricType(Class clazz) { return getSuperClassGenricType(clazz, 0); } public static Class getSuperClassGenricType(Class clazz, int index) { Type genType = clazz.getGenericSuperclass(); if (!(genType instanceof ParameterizedType)) { logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType"); return Object.class; } Type[] params = ((ParameterizedType)genType).getActualTypeArguments(); if ((index >= params.length) || (index < 0)) { logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + params.length); return Object.class; } if (!(params[index] instanceof Class)) { logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); return Object.class; } return (Class)params[index]; } public static IllegalArgumentException convertToUncheckedException(Exception e) { if (((e instanceof IllegalAccessException)) || ((e instanceof IllegalArgumentException)) || ((e instanceof NoSuchMethodException))) { return new IllegalArgumentException("Refelction Exception.", e); } return new IllegalArgumentException(e); } public static Field getFieldByFieldName(Object obj, String fieldName) { for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { try { return superClass.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { } } return null; } public static Object getValueByFieldName(Object obj, String fieldName) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field field = getFieldByFieldName(obj, fieldName); Object value = null; if (field != null) { if (field.isAccessible()) { value = field.get(obj); } else { field.setAccessible(true); value = field.get(obj); field.setAccessible(false); } } return value; } public static void setValueByFieldName(Object obj, String fieldName, Object value) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); if (field.isAccessible()) { field.set(obj, value); } else { field.setAccessible(true); field.set(obj, value); field.setAccessible(false); } } }
参考:
http://blog.csdn.net/synsdeng/article/details/78561139
http://blog.csdn.net/flamingsky007/article/details/7195399
http://fred-han.iteye.com/blog/1771395
可以在github上看,作者对这个问题的回复