当前位置: 首页 > 后端技术 > Java

mybatis拦截器

时间:2023-04-01 19:50:29 Java

Mybatis支持四种拦截器,可以从Mybatis初始化类Configuration中验证。具体包括:ParameterHandler拦截器ResultSetHandler拦截器StatementHandler拦截器Executor拦截器这四个拦截器的用途各不相同。当我们熟悉Mybatis的运行机制后,就比较容易理解了。目前,如果我们对Mybatis了解不多,也没关系,不影响我们初步了解Mybatis拦截器。四种拦截器我们不需要一下子全部了解,因为它们的工作机制和底层原理大致相同。今天我们以Executor拦截器为切入点,了解Mybatis拦截器的实现方法,并初步分析其实现原理。今天的目标是:利用Mybatis拦截器技术计算每条sql语句的执行时间,并在控制台打印出具体的sql语句和参数。在这个过程中,我们将学习:编写Mybatis拦截器。Mybatis拦截器注册。Mybatis拦截器的初始化过程。Mybatis拦截器是如何工作的。准备Springboot工程,并引入Mybatis,在pom文件中添加依赖:2.1.3然后配置数据库访问,建表,创建mapper.xml文件和mapper对象,写一个简单的sql获取mapper.xml中的数据,通过sql语句使用mapper对象获取数据.今天这篇文章的主要目标是拦截器,所以上面关于通过Mybatis获取数据库数据的代码就不贴出来了。编写拦截器Mybatis拦截器是AOP的一种具体实现。我们在上一篇文章中分析过,AOP的实现原理其实就是动态代理。java中实现动态代理有两种方式:cglib和javanative(我们前面有一篇文章专门分析了两者的区别),Mybatis拦截器是javanative方式实现的。其实我们实现的拦截器是java原生动态代理框架中回调对象的一部分。回调对象其实就是一个Plugin,Plugin对象持有Interceptor。Plugin的invoke方法是JDK动态代理中的回调方法,会调用Interceptor的拦截方法,所以Plugin的invoke方法其实类似于模板方法(这部分后面会分析)。所以Mybatis已经给我们安排好了,我们的拦截器只需要实现这个拦截方法即可。@Slf4j@Component@Intercepts(@Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}))publicclassmyInterceptor实现拦截器{@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{MappedStatementms=(MappedStatement)invocation.getArgs()[0];}对象参数=invocation.getArgs()[1];BoundSqlboundSql=ms.getBoundSql(param);字符串sql=boundSql.getSql();sql=sql.trim().replaceAll("\\s+","");log.info("sql:"+sql);log.info("参数:"+参数);长启动时间=System.currentTimeMillis();对象result=invocation.proceed();longendTime=System.currentTimeMillis();log.info("sql语句取:"+(endTime-startTime));返回结果;}}要实现的目标都在上面这一段代码中,一目了然。以下几点需要说明:@Intercepts注解:目的是告诉Mybatis当前拦截器的类型(开头提到的四种类型之一)、拦截方法的名称、方法参数。Invocation:调用拦截器时组装的包装对象,包括被代理对象(原始对象)、被代理方法、方法调用参数。通过Invocation.proceed()执行代理对象原有的方法,所以我们可以在方法前后添加自己的增强功能。比如计算SQL语句的执行时间,就是获取系统时间,计算方法执行前后的时间差。能。Executor有两个查询方法,我们需要知道应用程序最终会调用Executor的哪个查询方法,否则如果不匹配,则不会执行拦截。当然我们也可以对多个方法进行拦截。invocation.getArgs()[0]获取代理方法的第一个参数,依此类推...可以获取代理方法的所有参数,所以拦截器中可以有一个完整的代理方法的执行位置代理方法可以做拦截器理论上可以做的任何事情。好的,我们完成了拦截器代码。拦截器注册拦截器写完后,需要在Mybatis的InterceptorChain中进行注册才能生效。我们可以看出Mybatis的拦截器是一个链式的概念,所以我们可以实现多个拦截器,每个拦截器都可以达到自己的目的。拦截器的注册可以通过以下方式实现:在mybatis.xml文件中,通过plugins标签配置配置类,创建ConfigurationCustomizer类实现customize方法,将拦截器注册到SpringIoc容器中春季项目。我们目前是基于Springboot项目,所以在上面的代码中添加了@Component注解,通过第三种方式完成注册,简单方便。运行拦截器准备就绪,启动项目,随便运行一个数据查询方法:可以看到拦截器已经正常工作了。上面我们实现了一个简单的Executor拦截器,下面我们花点时间分析一下这个拦截器是如何工作的。拦截器的初始化如果不对Mybatis的初始化过程进行整体分析,完全理解拦截器的初始化过程有些困难,但是如果我们只看Mybatis初始化过程中与拦截器相关的部分,也是不是不可能。在Mybatis的初始化过程中,会通过SqlSessionFactoryBuilder创建SqlSessionFactory,SqlSessionFactory会持有Configuration对象。至于前面我们提到的Mybatis拦截器的注册,不管怎么注册,其目的无非是在Mybatis启动和初始化的过程中,将拦截器注册到Configuration对象中。比如我们上面提到的任何一种注册方式,SqlSessionFactoryBean最终都会将拦截器获取到plugins属性中,并在buildSqlSessionFactory()方法中将拦截器注册到Configuration对象中:if(!isEmpty(this.plugins)){Stream.of(this.plugins).forEach(plugin->{targetConfiguration.addInterceptor(plugin);LOGGER.debug(()->"Registeredplugin:'"+plugin+"'");});}//省略代码returnthis.sqlSessionFactoryBuilder.build(targetConfiguration);最后调用SqlSessionFactoryBuilder的build方法创建SqlSessionFactory,从源码中我们可以看到最终创建了DefaultSqlSessionFactory,并将Configuration对象作为参数传入:publicSqlSessionFactorybuild(Configurationconfig){}和DefaultSqlSessionFactory将保存配置对象:publicDefaultSqlSessionFactory(Configurationconfiguration){this.configuration=configuration;所以,Mybatis在初始化的过程中会拿到我们注册的拦截器,拦截器会在Configuration对象中注册拦截器,最后SqlSesscionFactory对象会持有Configuration对象,从而持有拦截器。拦截器是如何工作的#openSession我们来看看初始化后的拦截器最终是如何生效的。我们知道,一条数据库操作语句的执行,首先是调用SqlSessionFactory的openSession来获取sqlSession。上面我们已经看到DefaultSqlSessionFactory是在初始化过程中创建的,所以我们直接看DefaultSqlSessionFactory的openSession方法。最后,它会调用openSessionFromDataSource或openSessionFromConnection。两种方法的结构并没有太大区别,但具体的细节今天就不分析了。我们直接看openSessionFromDataSource:privateSqlSessionopenSessionFromDataSource(ExecutorTypeexecType,TransactionIsolationLevellevel,booleanautoCommit){Transactiontx=null;try{finalEnvironmentenvironment=configuration.getEnvironment();finalTransactionFactorytransactionFactory=getTransactionFactoryFromEnvironment(环境);tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCommit);最终执行器executor=configuration.newExecutor(tx,execType);返回新的DefaultSqlSession(配置、执行器、自动提交);}catch(Exceptione){closeTransaction(tx);//可能已经获取了一个连接,所以让我们调用close()throwExceptionFactory.wrapException("Erroropeningsession.Cause:"+e,e);}最后{ErrorContext.instance().reset();}}注意的重点放在finalExecutorexecutor=configuration.newExecutor(tx,execType)上,我们去看看下Configuraton的这个方法:publicExecutornewExecutor(Transactiontransaction,ExecutorTypeexecutorType){executorType=executorType==null?默认执行器类型:执行器类型;执行器类型=执行器类型==空?执行器类型.SIMPLE:执行器类型;执行者执行者;if(ExecutorType.BATCH)==exe{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);回归执行人;}方法最后阶段获取到Excutor后,调用interceptorChain.pluginAll,它会一一调用拦截器的plugin方法。plugin方法调用Plugin的wrap方法:publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){Map,Set>signatureMap=getSignatureMap(interceptor);班级<?>type=target.getClass();类[]interfaces=getAllInterfaces(type,signatureMap);if(interfaces.length>0){returnProxy.newProxyInstance(type.getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}返回目标;}最后通过动态代理返回一个对象的代理对象,回调对象是持有原对象签名、拦截器、拦截方法的Plugin对象,所以我们知道,openSession创建的DefaultSqlSession持有的Executor实际上是一个已经被拦截器处理过的代理对象。按照我们对JDK代理的理解,当最终调用Executor方法时,实际上是回调了创建代理对象时回调的invoke方法,即Plugin的invoke方法。拦截器是如何工作的#ExecutorExecution上一节分析了在openSession过程中Executor代理对象是如何创建的。接下来我们看一下具体的Executor的执行情况。在这个例子中,拦截了他的查询方法。其实我们已经知道执行查询方法的时候会调用Plugin的invoke方法。代码其实比较简单:@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{try{Setmethods=signatureMap.get(method.getDeclaringClass());if(methods!=null&&methods.contains(method)){returninterceptor.intercept(newInvocation(target,method,args));}returnmethod.invoke(target,args);}catch(Exceptione){throwExceptionUtil.unwrapThrowable(e);}}获取当前Executor对象所有注册的拦截方法,比较当前调用的方法是否为拦截方法,如果是则调用拦截器的拦截方法...也就是我们自己写的拦截器的拦截方法.否则,如果当前方法未配置为拦截,则将调用原始方法。在调用拦截器的intercept方法时,会创建一个Invocation对象,其中包含代理对象target、拦截方法、拦截方法的调用参数……等数据,作为参数传入。这就是为什么我们可以在拦截器方法中获取这些数据。OK...还是差了一点,就是如果配置了多个代理,调用顺序是个问题。其实从整体上看,Mybatis的源码感觉比Spring简单多了。拦截器注册后,存放在ArrayList中的InterceptorChain中,所以应该是乱序的。如果你想控制调用顺序,你应该想其他的方法。上一个SpringSecurity+JWT下一个Mybatis缓存机制