今天再工作的过程当中,在使用分页插件的时候再dao层使用了@Param注解导致了分页失效。先上没改之前的代码。
PaginationInterceptor
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Map;
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.SqlSource;
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.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import cn.jts.framework.component.Global;
import cn.jts.framework.persistence.Page;
import cn.jts.framework.utils.ReflectionUtils;
import cn.jts.framework.utils.StringUtils;
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) })
public class PaginationInterceptor extends BaseInterceptor {
private static final long serialVersionUID = 1L;
public Object intercept(Invocation invocation) throws Throwable {
final MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Object parameterObject = boundSql.getParameterObject();
String jdbcType = Global.getConfig("jdbc.type");
// 获取分页参数对象
Page<Object> page = null;
if (parameterObject != null) {
page = convertParameter(parameterObject, page);
}
// 如果设置了分页对象,则进行分页
if (page != null && page.getPageSize() != -1) {
if (StringUtils.isBlank(boundSql.getSql())) {
return null;
}
String originalSql = boundSql.getSql().trim();
// 得到总记录数
page.setCount(SQLHelper.getCount(originalSql, null, mappedStatement, parameterObject, boundSql, log, jdbcType));
// 分页查询 本地化对象 修改数据库注意修改实现
String pageSql = SQLHelper.generatePageSql(originalSql, page, DIALECT);
// if (log.isDebugEnabled()) {
// log.debug("PAGE SQL:" + StringUtils.replace(pageSql, "\n", ""));
// }
invocation.getArgs()[2] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), pageSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
// 解决MyBatis 分页foreach 参数失效 start
if (ReflectionUtils.getFieldValue(boundSql, "metaParameters") != null) {
MetaObject mo = (MetaObject) ReflectionUtils.getFieldValue(boundSql, "metaParameters");
ReflectionUtils.setFieldValue(newBoundSql, "metaParameters", mo);
}
// 解决MyBatis 分页foreach 参数失效 end
MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
invocation.getArgs()[0] = newMs;
}
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
String jdbcType = Global.getConfig("jdbc.type");
super.initProperties(properties, jdbcType);
}
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());
if (ms.getKeyProperties() != null) {
for (String keyProperty : ms.getKeyProperties()) {
builder.keyProperty(keyProperty);
}
}
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.cache(ms.getCache());
builder.useCache(ms.isUseCache());
return builder.build();
}
public static class BoundSqlSqlSource implements SqlSource {
BoundSql boundSql;
public BoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
public static Object mapToObject(Map<String, Object> paramMap, Class<?> beanClass) throws Exception {
if (paramMap == null)
return null;
Object obj = beanClass.newInstance();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
int mod = field.getModifiers();
if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
continue;
}
field.setAccessible(true);
if (paramMap.containsKey(field.getName())) {
field.set(obj, paramMap.get(field.getName()));
}
}
return obj;
}
}
SQLHelper
public class SQLHelper {
/**
* 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.DefaultParameterHandler
*
* @param ps 表示预编译的 SQL 语句的对象。
* @param mappedStatement MappedStatement
* @param boundSql SQL
* @param parameterObject 参数对象
* @throws java.sql.SQLException 数据库异常
*/
@SuppressWarnings("unchecked")
public static 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);
}
@SuppressWarnings("rawtypes")
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());
}
}
}
}
/**
* 查询总纪录数
* @param sql SQL语句
* @param connection 数据库连接
* @param mappedStatement mapped
* @param parameterObject 参数
* @param boundSql boundSql
* @return 总记录数
* @throws SQLException sql查询错误
*/
public static int getCount(final String sql, final Connection connection, final MappedStatement mappedStatement, final Object parameterObject, final BoundSql boundSql, Log log, String jdbcType) throws SQLException {
String dbName = jdbcType;
final String countSql;
if ("oracle".equals(dbName)) {
countSql = "select count(1) from (" + sql + ") tmp_count";
} else {
countSql = "select count(1) from (" + removeOrders(sql) + ") tmp_count";
}
Connection conn = connection;
PreparedStatement ps = null;
ResultSet rs = null;
try {
if (log.isDebugEnabled()) {
log.debug("COUNT SQL: " + StringUtils.replaceEach(countSql, new String[] { "\n", "\t" }, new String[] { " ", " " }));
}
if (conn == null) {
conn = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection();
}
ps = conn.prepareStatement(countSql);
BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), parameterObject);
// 解决MyBatis 分页foreach 参数失效 start
if (ReflectionUtils.getFieldValue(boundSql, "metaParameters") != null) {
MetaObject mo = (MetaObject) ReflectionUtils.getFieldValue(boundSql, "metaParameters");
ReflectionUtils.setFieldValue(countBS, "metaParameters", mo);
}
// 解决MyBatis 分页foreach 参数失效 end
SQLHelper.setParameters(ps, mappedStatement, countBS, parameterObject);
rs = ps.executeQuery();
int count = 0;
if (rs.next()) {
count = rs.getInt(1);
}
return count;
} finally {
if (rs != null) {
rs.close();
}
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
}
}
/**
* 根据数据库方言,生成特定的分页sql
* @param sql Mapper中的Sql语句
* @param page 分页对象
* @param dialect 方言类型
* @return 分页SQL
*/
public static String generatePageSql(String sql, Page<Object> page, Dialect dialect) {
if (dialect.supportsLimit()) {
return dialect.getLimitString(sql, page.getFirstResult(), page.getMaxResults());
} else {
return sql;
}
}
/**
* 去除qlString的select子句。
* @param hql
* @return
*/
@SuppressWarnings("unused")
private static String removeSelect(String qlString) {
int beginPos = qlString.toLowerCase().indexOf("from");
return qlString.substring(beginPos);
}
/**
* 去除hql的orderBy子句。
* @param hql
* @return
*/
@SuppressWarnings("unused")
private static String removeOrders(String qlString) {
Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(qlString);
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, "");
}
m.appendTail(sb);
return sb.toString();
}
}
BaseInterceptor
public abstract class BaseInterceptor implements Interceptor, Serializable {
private static final long serialVersionUID = 1L;
protected static final String PAGE = "page";
protected static final String DELEGATE = "delegate";
protected static final String MAPPED_STATEMENT = "mappedStatement";
protected Log log = LogFactory.getLog(this.getClass());
protected Dialect DIALECT;
/**
* 对参数进行转换和检查
* @param parameterObject 参数对象
* @param page 分页对象
* @return 分页对象
* @throws NoSuchFieldException 无法找到参数
*/
@SuppressWarnings("unchecked")
protected static Page<Object> convertParameter(Object parameterObject, Page<Object> page) {
try {
if (parameterObject instanceof Page) {
return (Page<Object>) parameterObject;
} else {
return (Page<Object>) ReflectionUtils.getFieldValue(parameterObject, PAGE);
}
} catch (Exception e) {
return null;
}
}
/**
* 设置属性,支持自定义方言类和制定数据库的方式
* <code>dialectClass</code>,自定义方言类。可以不配置这项
* <ode>dbms</ode> 数据库类型,插件支持的数据库
* <code>sqlPattern</code> 需要拦截的SQL ID
* @param p 属性
*/
protected void initProperties(Properties p, String jdbcType) {
Dialect dialect = null;
String dbType = jdbcType;
if ("mysql".equals(dbType)) {
dialect = new MySQLDialect();
}
if (dialect == null) {
throw new RuntimeException("mybatis dialect error.");
}
DIALECT = dialect;
}
}
在我在调试代码的过程中发现了就是这一段代码出了问题:
*
我们先看看没有加@Param注解代码是如何走的:
这里再拦截当中invocation.getArgs()[1];是解析到了dao层的参数。所以往下走:
这里也是能成功解析到了Pag对象。
接着们看一下加了@Param注解代码是如何走的(导致分页失效):
之前我们在之前invocation.getArgs()[1]是拦截到的实体类这里确是在实体类外面包了一成Map所以导致了我们解析Page对象失败了导致了分页失效:
然后为了解决这个问题我特意看了一下@Param注解源码实现如下:
跟踪到源码进入org.apache.ibatis.binding.MapperMethod
MapperMethod类是处理mapper接口中方法的真正处理器,该类内部的execute明确了代理的方法参数要怎么处理,查询得到的结果怎么封装然后返回
public class MapperMethod {
//对执行的SQL标签的封装,包含SQL类型和任务的完整ID等信息
private final SqlCommand command;
//代理方法的签名,其内部封装了一系列操作,如方法多参数时对@Param注解的处理
private final MethodSignature method;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) { //针对DML/DQL操作执行
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}
这里我们注意一下这段代码:
method.convertArgsToSqlCommandParam(args)获取到实际输入的参数,获取到的是个Map集合,对于单一实体例如User那么获取到的就是该实体
然后进入convertArgsToSqlCommandParam
在进入:
好了看到这段代码,意思如下在使用了这个注解的实体类,先会获取到实体类然后把丢到一个ParaMap当中多个实体类也会循环丢入ParaMap,这样就可以共通过@Param(value = “attributeBo”)(K,V)的形式来获取到对应的实体类。!
解决方案
当知道使用@Param的时候回吧实体封装到一个ParaMap数组里面之后,这样我们只需把实体类从ParaMap解析出来就可以了:
新增代码:修改BaseInterceptor类中的convertParameter方法
if(parameterObject instanceof Map){
Map<String, Object> param = (Map<String, Object>)parameterObject;
Collection<Object> values = param.values() ;// 得到全部的value
Iterator<Object> iter = values.iterator() ;
while(iter.hasNext()){
Object mapValue = iter.next() ;
if(mapValue != null){
parameterObject = mapValue;
}
}
}
全部代码:修改完之后BaseInterceptor全部代码:
public abstract class BaseInterceptor implements Interceptor, Serializable {
private static final long serialVersionUID = 1L;
protected static final String PAGE = "page";
protected static final String DELEGATE = "delegate";
protected static final String MAPPED_STATEMENT = "mappedStatement";
protected Log log = LogFactory.getLog(this.getClass());
protected Dialect DIALECT;
/**
* 对参数进行转换和检查
* @param parameterObject 参数对象
* @param page 分页对象
* @return 分页对象
* @throws NoSuchFieldException 无法找到参数
*/
@SuppressWarnings("unchecked")
protected static Page<Object> convertParameter(Object parameterObject, Page<Object> page) {
try {
if (parameterObject instanceof Page) {
return (Page<Object>) parameterObject;
} else {
if(parameterObject instanceof Map){
Map<String, Object> param = (Map<String, Object>)parameterObject;
Collection<Object> values = param.values() ;// 得到全部的value
Iterator<Object> iter = values.iterator() ;
while(iter.hasNext()){
Object mapValue = iter.next() ;
if(mapValue != null){
parameterObject = mapValue;
}
}
}
return (Page<Object>) ReflectionUtils.getFieldValue(parameterObject, PAGE);
}
} catch (Exception e) {
return null;
}
}
/**
* 设置属性,支持自定义方言类和制定数据库的方式
* <code>dialectClass</code>,自定义方言类。可以不配置这项
* <ode>dbms</ode> 数据库类型,插件支持的数据库
* <code>sqlPattern</code> 需要拦截的SQL ID
* @param p 属性
*/
protected void initProperties(Properties p, String jdbcType) {
Dialect dialect = null;
String dbType = jdbcType;
if ("mysql".equals(dbType)) {
dialect = new MySQLDialect();
}
if (dialect == null) {
throw new RuntimeException("mybatis dialect error.");
}
DIALECT = dialect;
}
}
添加之后我们再看一下效果:
ok。没事收工