700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 使用MyBatis 拦截器(Interceptor)实现自定义分页插件 使用@Param注解使得分页失效

使用MyBatis 拦截器(Interceptor)实现自定义分页插件 使用@Param注解使得分页失效

时间:2021-04-14 21:18:40

相关推荐

使用MyBatis 拦截器(Interceptor)实现自定义分页插件 使用@Param注解使得分页失效

今天再工作的过程当中,在使用分页插件的时候再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.ponent.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 参数失效 startif (ReflectionUtils.getFieldValue(boundSql, "metaParameters") != null) {MetaObject mo = (MetaObject) ReflectionUtils.getFieldValue(boundSql, "metaParameters");ReflectionUtils.setFieldValue(newBoundSql, "metaParameters", mo);}// 解决MyBatis 分页foreach 参数失效 endMappedStatement 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 参数失效 startif (ReflectionUtils.getFieldValue(boundSql, "metaParameters") != null) {MetaObject mo = (MetaObject) ReflectionUtils.getFieldValue(boundSql, "metaParameters");ReflectionUtils.setFieldValue(countBS, "metaParameters", mo);}// 解决MyBatis 分页foreach 参数失效 endSQLHelper.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 sqlMapper中的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 = pile("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() ;// 得到全部的valueIterator<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() ;// 得到全部的valueIterator<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。没事收工

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。