mybatis拦截处理
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis拦截处理相关的知识,希望对你有一定的参考价值。
参考技术A 在工作中,我们常常需要在不改变源码的条件下对sql进行一些修改。在这过程中用到的技术原理就是mybatis的拦截器(对于mybatis的内置对象笔者也还知之甚少,但这个不耽误咱使用mybatis的拦截器)。
package com.interceptors.mybatis;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.Properties;
@Component
@Intercepts(
@Signature(
type = StatementHandler.class, method ="prepare", args = Connection.class, Integer.class)
)
@Slf4j
public class MybatisSqlInterceptorimplements Interceptor
@Override
public Object intercept(Invocation invocation)throws Throwable
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());
//先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
//id为执行的mapper方法的全路径名
String id = mappedStatement.getId();
//sql语句类型 select、delete、insert、update
String sqlCommandType = mappedStatement.getSqlCommandType().toString();
BoundSql boundSql = statementHandler.getBoundSql();
//获取到原始sql语句
String sql = boundSql.getSql();
String mSql = sql;
//TODO 修改位置
//注解逻辑判断 添加注解了才拦截
Class classType = Class.forName(mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf(".")));
String mName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") +1, mappedStatement.getId().length());
for (Method method : classType.getDeclaredMethods())
if (method.isAnnotationPresent(InterceptAnnotation.class) && mName.equals(method.getName()))
InterceptAnnotation interceptorAnnotation = method.getAnnotation(InterceptAnnotation.class);
if (interceptorAnnotation.flag())
//重新设置mapper接口的参数或者对sql进行改造
boundSql.setAdditionalParameter("","");
//通过反射修改sql语句
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, mSql);
return invocation.proceed();
@Override
public Object plugin(Object target)
if (targetinstanceof StatementHandler)
return Plugin.wrap(target,this);
else
return target;
@Override
public void setProperties(Properties properties)
package com.interceptors.mybatis;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD, ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface InterceptAnnotation
boolean flag()default true;
(对于mybatis拦截器的使用还需要了解mybatis更多的内置方法,本篇文章也只是mybatis拦截器的一点皮毛)
mybatis拦截器(上)
1. 拦截器注解
1. mybatis自定义拦截器实现步骤:
- 实现
org.apache.ibatis.plugin.Interceptor
接口。 - 添加拦截器注解
org.apache.ibatis.plugin.Intercepts
。 - 配置文件中添加拦截器。
2. 在mybatis中可被拦截的类型有四种(按照拦截顺序):
- Executor:拦截执行器的方法。
- ParameterHandler:拦截参数的处理。
- ResultHandler:拦截结果集的处理。
- StatementHandler:拦截Sql语法构建的处理。
1. 不同拦截类型执行顺序:
2. 多个插件拦截的顺序?
需要注意的是,因为拦截器Aa和拦截器Bb均是拦截的StatementHandler对象,所以拦截器B在此获取StatementHandler的时候,获取的是代理对象。
3. 多个插件plugin()和intercept()方法的执行顺序
先执行每个插件的plugin方法,若是@Intercepts注解标明需要拦截该对象,那么生成类型对象的代理对象。(即使该插件需要拦截该类型对象,但是依旧会执行下一个插件的plugin方法)。知道执行完毕所有的plugin方法。在执行每个Intercept方法。
3. 拦截器注解的作用:
自定义拦截器必须使用mybatis提供的注解来声明我们要拦截的类型对象。
Mybatis插件都要有Intercepts [in特赛婆斯]
注解来指定要拦截哪个对象哪个方法。我们知道,Plugin.wrap方法会返回四大接口对象的代理对象,会拦截所有的方法。在代理对象执行对应方法的时候,会调用InvocationHandler处理器的invoke方法。
4. 拦截器注解的规则:
具体规则如下:
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
- @Intercepts:标识该类是一个拦截器;
- @Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;
2.1 type:对应四种类型中的一种;
2.2 method:对应接口中的哪类方法(因为可能存在重载方法);
2.3 args:对应哪一个方法;
5. 拦截器可拦截的方法:
拦截的类 | 拦截的方法 |
---|---|
Executor | update, query, flushStatements, commit, rollback,getTransaction, close, isClosed |
ParameterHandler | getParameterObject, setParameters |
StatementHandler | prepare, parameterize, batch, update, query |
ResultSetHandler | handleResultSets, handleOutputParameters |
2. 拦截器方法
2.1 官方插件开发方式
@Intercepts({@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class TestInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget(); //被代理对象
Method method = invocation.getMethod(); //代理方法
Object[] args = invocation.getArgs(); //方法参数
// do something ...... 方法拦截前执行代码块
Object result = invocation.proceed();
// do something .......方法拦截后执行代码块
return result;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}
2.2 拦截器的方法
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
2.2.1 setProperties方法
如果我们的拦截器需要一些变量对象,而且这个对象是支持可配置的。
类似于Spring中的@Value("${}")从application.properties文件中获取。
使用方法:
<plugin interceptor="com.plugin.mybatis.MyInterceptor">
<property name="username" value="xxx"/>
<property name="password" value="xxx"/>
</plugin>
方法中获取参数:properties.getProperty("username");
问题:但是为什么不直接使用@Value("${}") 获取变量?
解答:因为mybatis框架本身就是一个可以独立使用的框架,没有像Spring这种做了很多的依赖注入。
2.2.2 plugin方法
这个方法的作用是就是让mybatis判断,是否要进行拦截,然后做出决定是否生成一个代理。
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
需要注意的是:每经过一个拦截器对象都会调用插件的plugin方法,也就是说,该方法会调用4次。根据@Intercepts注解来决定是否进行拦截处理。
问题1:
Plugin.wrap(target, this)
方法的作用?
解答:判断是否拦截这个类型对象(根据@Intercepts注解决定),然后决定是返回一个代理对象还是返回原对象。
故我们在实现plugin方法时,要判断一下目标类型,是本插件要拦截的对象时才执行Plugin.wrap方法,否则的话,直接返回目标本身。
问题2:拦截器代理对象可能经过多层代理,如何获取到真实的拦截器对象?
/**
* <p>
* 获得真正的处理对象,可能多层代理.
* </p>
*/
2.2.3 intercept(Invocation invocation)方法
我们知道,mybatis只能拦截四种类型的对象。而intercept
方法便是处理拦截到的对象。比如我们要拦截StatementHandler#query(Statement st,ResultHandler rh)
方法,那么Invocation
就是这个对象,Invocation
中有三个参数。
- target:StatementHandler;
- method :query;
- args[]:Statement st,ResultHandler rh
org.apache.ibatis.reflection.SystemMetaObject#forObject
:方便的获取对象中的值。
案例:将参数拼接到sql语句。
因为已经执行了ParameterHandler拦截器,故Statement对象已经是完全拼接好的SQL语句。