Executor拦截器高级教程QueryInterceptor的规范
Executor拦截器高级教程-QueryInterceptor规范
这篇文档涉及下面几个方面
- 1.Executorquery方法介绍
- 2.拦截器配置和调用顺序
- 3.拦截query方法的技巧
- 4.拦截query方法的规范
- 5.如何配置不同的Executor插件
1.Executorquery方法介绍
在MyBatis的拦截器的文档部分,我们知道Executor中的query方法可以被拦截,如果你真正写过这个方法的拦截器,你可能会知道在Executor中的query方法有两个:
List query( MappedStatementms, Objectparameter, RowBoundsrowBounds, ResultHandlerresultHandler, CacheKeycacheKey, BoundSqlboundSql)throwsSQLException; List query( MappedStatementms, Objectparameter, RowBoundsrowBounds, ResultHandlerresultHandler)throwsSQLException;
这两个方法的区别是第一个方法多两个参数CacheKey和BoundSql,在多数情况下,我们用拦截器的目的就是针对SQL做处理,如果能够拦截第一个方法,可以直接得到BoundSql对象,就会很容易的得到执行的SQL,也可以对SQL做处理。
虽然想的很好,但是MyBatis提供的Exctutor实现中,参数多的这个query方法都是被少的这个query方法在内部进行调用的。
在CachingExecutor中:
publicList query( MappedStatementms, Objectparameter, RowBoundsrowBounds, ResultHandlerresultHandler)throwsSQLException{ BoundSqlboundSql=ms.getBoundSql(parameterObject); CacheKeykey=createCacheKey(ms,parameterObject,rowBounds,boundSql); returnquery(ms,parameterObject,rowBounds,resultHandler,key,boundSql); }
在BaseExecutor中:
publicList query( MappedStatementms, Objectparameter, RowBoundsrowBounds, ResultHandlerresultHandler)throwsSQLException{ BoundSqlboundSql=ms.getBoundSql(parameter); CacheKeykey=createCacheKey(ms,parameter,rowBounds,boundSql); returnquery(ms,parameter,rowBounds,resultHandler,key,boundSql); }
上面这两个方法一样。由于第一个query方法在这里是内部调用,并且我们所有的拦截器都是层层代理的CachingExecutor或基于BaseExecutor的实现类,所以我们能拦截的就是参数少的这个方法。
分页插件开始从Executor拦截开始就一直是拦截的参数少的这个方法。但是从5.0版本开始,query的这两个方法都可以被拦截了。在讲这个原理之前,我们先了解一下拦截器的执行顺序。
2.拦截器配置和调用顺序
拦截器的调用顺序分为两大种,第一种是拦截的不同对象,例如拦截Executor和拦截StatementHandler就属于不同的拦截对象,这两类的拦截器在整体执行的逻辑上是不同的,在Executor中的query方法执行过程中,会调用下面的代码:
publicList doQuery( MappedStatementms, Objectparameter, RowBoundsrowBounds, ResultHandlerresultHandler, BoundSqlboundSql)throwsSQLException{ Statementstmt=null; try{ Configurationconfiguration=ms.getConfiguration(); StatementHandlerhandler=configuration.newStatementHandler(wrapper,ms,parameter,rowBounds,resultHandler,boundSql); stmt=prepareStatement(handler,ms.getStatementLog()); returnhandler. query(stmt,resultHandler); }finally{ closeStatement(stmt); } }
在这段代码中,才会轮到StatementHandler去执行,StatementHandler属于Executor执行过程中的一个子过程。所以这两种不同类别的插件在配置时,一定是先执行Executor的拦截器,然后才会轮到StatementHandler。所以这种情况下配置拦截器的顺序就不重要了,在MyBatis逻辑上就已经控制了先后顺序。
第二种拦截器的顺序就是指拦截同一种对象的同一个方法,例如都拦截Executor的query方法,这时你配置拦截器的顺序就会对这里有影响了。假设有如下几个拦截器,都是拦截的Executor的query方法。
在org.apache.ibatis.session.Configuration中有如下方法:
publicvoidaddInterceptor(Interceptorinterceptor){ interceptorChain.addInterceptor(interceptor); }
MyBatis会按照拦截器配置的顺序依次添加到interceptorChain中,其内部就是List
publicExecutornewExecutor(Transactiontransaction,ExecutorTypeexecutorType){ executorType=executorType==null?defaultExecutorType:executorType; executorType=executorType==null?ExecutorType.SIMPLE:executorType; Executorexecutor; if(ExecutorType.BATCH==executorType){ executor=newBatchExecutor(this,transaction); }elseif(ExecutorType.REUSE==executorType){ executor=newReuseExecutor(this,transaction); }else{ executor=newSimpleExecutor(this,transaction); } if(cacheEnabled){ executor=newCachingExecutor(executor); } executor=(Executor)interceptorChain.pluginAll(executor); returnexecutor; }
在调用interceptorChain.pluginAll之前,executor就是前一节中的CachingExecutor或基于BaseExecutor的实现类。然后看interceptorChain.pluginAll方法:
publicObjectpluginAll(Objecttarget){ for(Interceptorinterceptor:interceptors){ target=interceptor.plugin(target); } returntarget; }
前面我们配置拦截器的顺序是1,2,3。在这里也会按照1,2,3的顺序被层层代理,代理后的结构如下:
Interceptor3:{ Interceptor2:{ Interceptor1:{ target:Executor } } }
从这个结构应该就很容易能看出来,将来执行的时候肯定是按照3>2>1>Executor>1>2>3的顺序去执行的。可能有些人不知道为什么3>2>1>Executor之后会有1>2>3,这是因为使用代理时,调用完代理方法后,还能继续进行其他处理。处理结束后,将代理方法的返回值继续往外返回即可。例如:
Interceptor3前置处理 Objectresult=Interceptor2..query(4个参数方法); Interceptor3后续处理 returnresult;
对于Interceptor2.invoke方法也是相同的逻辑:
Interceptor2前置处理 Objectresult=Interceptor1..query(4个参数方法); Interceptor2后续处理 returnresult;
同理Interceptor1.invoke:
Interceptor1前置处理 Objectresult=executor.query(4个参数方法); Interceptor1后续处理 returnresult;
叠加到一起后,如下:
Interceptor3前置处理 Interceptor2前置处理 Interceptor1前置处理 Objectresult=executor.query(4个参数方法); Interceptor1后续处理 Interceptor2后续处理 Interceptor3后续处理 returnresult;
所以这个顺序就是3>2>1>Executor>1>2>3。
在你弄清楚这个逻辑后,再继续往下看,因为后面的技巧会颠覆这个逻辑,所以才会有后面的规范以及如何配置不同的插件。
3.拦截query方法的技巧
上一节的内容中,对拦截器的用法是最常见的一种用法,所以才会出现这种都能理解的执行顺序。但是分页插件5.0不是这样,这个插件颠覆了这种顺序,这种颠覆其实也很普通,这也是本节要说的技巧。
在我写作MyBatis技术书籍的过程中(还没写完,已经因为分页插件占用了几周的写作时间),我就在考虑为什么不能拦截第一个query(6个参数的)方法,如果能拦截这个方法,就可以直接拿到BoundSql,然后处理SQL就很容易实现其他的操作。
在第1节介绍为什么第一个query方法不能被拦截时,是因为下面这段代码:
publicList query( MappedStatementms, Objectparameter, RowBoundsrowBounds, ResultHandlerresultHandler)throwsSQLException{ BoundSqlboundSql=ms.getBoundSql(parameter); CacheKeykey=createCacheKey(ms,parameter,rowBounds,boundSql); returnquery(ms,parameter,rowBounds,resultHandler,key,boundSql); }
既然CachingExecutor或基于BaseExecutor的实现类只是这么简单的调用两个方法得到了BoundSql和Cachekey,我们为什么不直接替代他们呢?
所以我们可以有类似下面的拦截器用法:
@Intercepts(@Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})) publicclassQueryInterceptorimplementsInterceptor{ @Override publicObjectintercept(Invocationinvocation)throwsThrowable{ Object[]args=invocation.getArgs(); MappedStatementms=(MappedStatement)args[0]; ObjectparameterObject=args[1]; RowBoundsrowBounds=(RowBounds)args[2]; ResultHandlerresultHandler=(ResultHandler)args[3]; Executorexecutor=(Executor)invocation.getTarget(); BoundSqlboundSql=ms.getBoundSql(parameterObject); //可以对参数做各种处理 CacheKeycacheKey=executor.createCacheKey(ms,parameterObject,rowBounds,boundSql); returnexecutor.query(ms,parameterObject,rowBounds,resultHandler,cacheKey,boundSql); } @Override publicObjectplugin(Objecttarget){ returnPlugin.wrap(target,this); } @Override publicvoidsetProperties(Propertiesproperties){ } }
这个拦截器直接替代了原有Executor的部分逻辑,直接去调用了6个参数的方法,因而导致4个参数的后续方法被跳过了。但是由于这里的executor是代理对象,所以6个参数的query方法可以被代理了,这就扰乱了上一节中的执行顺序。
在上一节拦截器的例子中,做简单修改,将ExecutorQueryInterceptor2换成上面的QueryInterceptor,配置如下:
代理后的结构如下:
Interceptor3:{ QueryInterceptor:{ Interceptor1:{ target:Executor } } }
这时,调用顺序就变了,Interceptor3执行顺序如下:
Interceptor3前置处理 Objectresult=QueryInterceptor.query(4个参数方法); Interceptor3后续处理 returnresult;
QueryInterceptor.invoke执行逻辑如下:
Interceptor2前置处理 Objectresult=executor.query(6个参数方法); Interceptor2后续处理 returnresult;
在QueryInterceptor中,没有继续执行4个参数方法,而是执行了6个参数方法。但是Interceptor1拦截的4个参数的方法,所以Interceptor1就被跳过去了,整体的执行逻辑就变成下面这样了:
Interceptor3前置处理 Interceptor2前置处理 Objectresult=executor.query(6个参数方法); Interceptor2后续处理 Interceptor3后续处理 returnresult;
如果Interceptor1拦截的是6个参数的方法,因为QueryInterceptor获取的是Interceptor1代理的executor对象,那么Interceptor1就会被QueryInterceptor继续执行下去。
分页插件就是类似QueryInterceptor的执行逻辑,所以当你使用5.0版本之后的插件时,如果你还需要配置其他Executor的query插件,你就会遇到一些问题(可以解决,继续往下看)。
如果你是自己开发的插件,那么你按照下一节的规范去开发也不会遇到问题。如果你使用的其他人提供的插件,按照第5节的配置顺序也能解决问题。
4.拦截query方法的规范
QueryInterceptor的逻辑就是进去的是4个参数的方法,出去的是6个参数的方法。这种处理方法不仅仅不方便和一般的Excutor拦截器搭配使用,当出现两个以上类似QueryInterceptor的插件时,由于接口变了,类似QueryInterceptor插件也无法连贯的执行下去。因而有必要解决这个问题。解决的办法就是使用统一的规范。经过规范后QueryInterceptor如下:
@Intercepts( { @Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}), @Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class,CacheKey.class,BoundSql.class}), } ) publicclassQueryInterceptorimplementsInterceptor{ @Override publicObjectintercept(Invocationinvocation)throwsThrowable{ Object[]args=invocation.getArgs(); MappedStatementms=(MappedStatement)args[0]; ObjectparameterObject=args[1]; RowBoundsrowBounds=(RowBounds)args[2]; ResultHandlerresultHandler=(ResultHandler)args[3]; Executorexecutor=(Executor)invocation.getTarget(); CacheKeycacheKey; BoundSqlboundSql; //由于逻辑关系,只会进入一次 if(args.length==4){ //4个参数时 boundSql=ms.getBoundSql(parameterObject); cacheKey=executor.createCacheKey(ms,parameterObject,rowBounds,boundSql); }else{ //6个参数时 cacheKey=(CacheKey)args[4]; boundSql=(BoundSql)args[5]; } //TODO自己要进行的各种处理 //注:下面的方法可以根据自己的逻辑调用多次,在分页插件中,count和page各调用了一次 returnexecutor.query(ms,parameterObject,rowBounds,resultHandler,cacheKey,boundSql); } @Override publicObjectplugin(Objecttarget){ returnPlugin.wrap(target,this); } @Override publicvoidsetProperties(Propertiesproperties){ } }
注意两个变化,第一个就是拦截器签名同时拦截了4个和6个参数的方法,这样不管那个插件在前在后都会被执行。
第二个变化就是这段代码:
CacheKeycacheKey; BoundSqlboundSql; //由于逻辑关系,只会进入一次 if(args.length==4){ //4个参数时 boundSql=ms.getBoundSql(parameterObject); cacheKey=executor.createCacheKey(ms,parameterObject,rowBounds,boundSql); }else{ //6个参数时 cacheKey=(CacheKey)args[4]; boundSql=(BoundSql)args[5]; }
如果这个插件配置的靠后,是通过4个参数方法进来的,我们就获取这两个对象。如果这个插件配置的靠前,已经被别的拦截器处理成6个参数的方法了,那么我们直接从args中取出这两个参数直接使用即可。取出这两个参数就保证了当其他拦截器对这两个参数做过处理时,这两个参数在这里会继续生效。
假设有个排序插件和分页插件,排序插件将BoundSql修改为带排序的SQL后,SQL会继续交给分页插件使用。分页插件的分页SQL执行时,会保留排序去执行,这样的规范就保证了两个插件都能正常的执行下去。
所以如果大家想要使用这种方式去实现拦截器,建议大家遵守这个规范。
这个规范对于已经存在的插件来说就没法控制了,但是仍然可以通过配置顺序来解决。
5.如何配置不同的Executor插件
当引入类似QueryInterceptor插件时,由于扰乱了原有的插件执行方式,当配置Executor顺序不对时会导致插件无法生效。
第4节中的例子:
首先执行顺序为3>Query>1>Executor,由于Query是4或6个参数进来,6个参数出去。所以在Query前面执行的拦截器必须是4个的(Query规范拦截器先后都能执行,需要根据逻辑配置先后)参数的,在Query后面执行的拦截器必须是6个参数的。
这个顺序对应到配置顺序时,也就是4个参数的配置在QueryInterceptor拦截器的下面,6个参数的配置在QueryInterceptor拦截器的上面。按照这个顺序进行配置时,就能保证拦截器都执行。
如果你想获得如分页插件(QueryInterceptor规范)执行的SQL,你就得按照QueryInterceptor规范去实现,否则只能配置在分页插件的下面,也就只能获得分页处理前的SQL。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。如果你想了解更多相关内容请查看下面相关链接
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。