MyBatis通用Mapper与分页PageHelper混淆报错问题

背景

当同时引入通用MapperPageHelper两款插件的时候,会存在报错的可能。

如果像这样,先执行通用Mapper,再执行分页插件就会出错

		
		<plugin interceptor="com.github.abel533.mapperhelper.MapperInterceptor">
			<property name="mappers" value="com.github.abel533.mapper.Mapper"/>
		plugin>

		
		<plugin interceptor="com.github.pagehelper.PageHelper">
			<property name="dialect" value="mysql"/>
		plugin>

原因解析

Mybatis中存在一个拦截器的回调接口,可以在拦截Myabatis四大杀器Executor、StatementHandler,ParameterHandler,ResultHandler。为什么只能拦截这四个接口?,因为它们都调用了拦截器栈。
Configuration中实例化了这几个组件:

 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? this.defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Object executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
        } else {
            executor = new SimpleExecutor(this, transaction);
        }
        if (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }
        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }
 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }
 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }

上面的四个方法都调用了pluginAll,interceptorChain是一种责任链的设计模式

当自定的interceptor加入到拦截器链中,就可以获取到上面四个对象,通过拿到对象的属性进一步扩展,例如修改SQL,大部分的情况下都是针对SQL进行自定义的扩展,PageHelper通用Mapper就是这样处理的。

下面我们来谈谈为什么出错?
这个问题出在通用Mapper这里
MapperInterceptor在做拦截的时候会将MappedStatement的执行对象替换为MapperProvider对象,MapperProvider是通用Mapper自己定义的,在原有的基础上进行封装增强,为我们封装了一些常用的CRUD操作,所以我们一般只需要继承通用Mapper的接口就可以实现大部分逻辑。但是它的实例化必须依赖带参数的构造函数

public class MapperProvider extends MapperTemplate {
  public MapperProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
       super(mapperClass, mapperHelper);
   }

而分页插件需要调用的原生的MappedStatement对象来完成一些操作,在创建SqlSource对象时,利用反射来获取SQL

private SqlSource createSqlSource(Object parameterObject) {
        try {
            String sql;
            if (this.providerTakesParameterObject) {
                sql = (String)this.providerMethod.invoke(this.providerType.newInstance(), parameterObject);
            } else {
                sql = (String)this.providerMethod.invoke(this.providerType.newInstance());
            }

            Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
            StaticSqlSource sqlSource = (StaticSqlSource)this.sqlSourceParser.parse(sql, parameterType, new HashMap());
            return new OrderByStaticSqlSource(sqlSource);
        } catch (Exception var5) {
            throw new BuilderException("Error invoking SqlProvider method (" + this.providerType.getName() + "." + this.providerMethod.getName() + ").  Cause: " + var5, var5);
        }
    }

重点在sql = (String)this.providerMethod.invoke(this.providerType.newInstance(),这个调用无参构造器来实例化的,现在通过拦截器链调用到这里就providerType就变为MapperProvider,前面提到过的MapperProvider没有默认的构造函数支持,所以创建失败。


解决方案

调整顺序先用分页插件,再用通用mapper

		
		<plugin interceptor="com.github.pagehelper.PageHelper">
			<property name="dialect" value="mysql"/>
		plugin>
		
		<plugin interceptor="com.github.abel533.mapperhelper.MapperInterceptor">
			<property name="mappers" value="com.github.abel533.mapper.Mapper"/>
		plugin>		

你可能感兴趣的:(瓜哇笔记系列)