欢迎大家来到IT世界,在知识的湖畔探索吧!
开发MyBatis自定义Interceptor拦截器的使用
一、使用背景
笔者前几天分享的在Spring Cloud架构中,关于数据权限的处理。笔者在团队中采用的就是通过MyBatis拦截器,拦截sql语句,动态组装sql来完成添加数据权限的配置,本文笔者分享MyBatis自定义Interceptor的使用。
二、拦截器注解
mybatis实现自定义拦截器实现过程:
(1)实现org.apache.ibatis.plugin.Interceptor接口;
(2)添加拦截器注解:org.apache.ibatis.plugin.Intercepts;
(3) 配置文件中添加拦截器。
在mybatis中能够被拦截的类型有四种:
(1)Executor:拦截执行器的方法;
(2)ParameterHandler:拦截参数的处理;
(3)ResultHandler:拦截结果集的处理;
(4)StatementHandler:拦截sql语法构建的处理;
不同类型执行的顺序过程:
多个插件的拦截顺序:
而拦截器对象的处理过程:
多个插件plugin和intercept方法的执行顺序:
先执行每个插件的plugin方法,如果@Intercepts注解表明能够拦截该对象,那么生成类型对象的代理对象。
2.1拦截器注解的规则
具体规则:
@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:指明自定义拦截器需要拦截哪一个类型,哪一个方法;
(1) type:对应四种类型中的一种;
(2) method:对应接口中的哪类方法(因为可能存在重载方法);
(3) args:对应哪一个方法;
拦截器可以拦截的方法:
2.2拦截器的方法
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
2.2.1setProperties
如果我们的拦截器需要一些变量对象,使用方法:
<plugin interceptor=”com.plugin.mybatis.MyInterceptor”>
<property name=”username” value=”xxx”/>
<property name=”password” value=”xxx”/>
</plugin>
在方法中获取参数:properties.getProperty(“username”);
2.2.2plugin方法
这个方法的作用,让MyBatis判断,是否要进行拦截,然后决定是否生成一个代理:
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
值得注意的是:每经过一个拦截器对象都会调用插件的plugin方法,也就是说,该方法会调用4次。根据@Intercepts注解来决定是否进行拦截处理。
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语句:
@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})
})
例如:
@Slf4j
public class Aa implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Statement statement;
//获取方法参数
Object firstArg = invocation.getArgs()[0];
if (Proxy.isProxyClass(firstArg.getClass())) {
statement = (Statement) SystemMetaObject.forObject(firstArg).getValue(“h.statement”);
} else {
statement = (Statement) firstArg;
}
MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
//获取Statement对象(sql语法已经构建完毕)
statement = (Statement) stmtMetaObj.getValue(“stmt.statement”);
//获取sql语句
String originalSql = statement.toString();
//将原始sql中的空白字符(\s包括换行符,制表符,空格符)替换为” “
originalSql = originalSql.replaceAll(“[\\s]+”, ” “);
//只获取sql的select/update/insert/delete开头的sql
int index = indexOfSqlStart(originalSql);
if (index > 0) {
originalSql = originalSql.substring(index);
}
// 计算执行 SQL 耗时
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long timing = System.currentTimeMillis()- start;
//获取MapperStatement对象,获取到sql的详细信息
Object realTarget = realTarget(invocation.getTarget());
//获取metaObject对象
MetaObject metaObject = SystemMetaObject.forObject(realTarget);
//获取MappedStatement对象
MappedStatement ms = (MappedStatement) metaObject.getValue(“delegate.mappedStatement”);
StringBuilder formatSql = new StringBuilder()
.append(” Time:”).append(timing)
//获取Mapper信息和方法信息
.append(” ms – ID:”).append(ms.getId())
.append(“Execute SQL:”)
.append(originalSql);
//打印sql信息
log.info(formatSql.toString());
return result;
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties prop) {
}
/**
* 获取sql语句开头部分
*
* @param sql
* @return
*/
private int indexOfSqlStart(String sql) {
String upperCaseSql = sql.toUpperCase();
Set<Integer> set = new HashSet<>();
set.add(upperCaseSql.indexOf(“SELECT “));
set.add(upperCaseSql.indexOf(“UPDATE “));
set.add(upperCaseSql.indexOf(“INSERT “));
set.add(upperCaseSql.indexOf(“DELETE “));
set.remove(-1);
if (CollectionUtils.isEmpty(set)) {
return -1;
}
List<Integer> list = new ArrayList<>(set);
list.sort(Comparator.naturalOrder());
return list.get(0);
}
/**
* <p>
* 获得真正的处理对象,可能多层代理.
* </p>
*/
@SuppressWarnings(“unchecked”)
public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue(“h.target”));
}
return (T) target;
}
}
三、拦截器实例
MyBatis配置:
添加拦截器:
整体mybatis配置如下
这就是MyBatis拦截器的实例
四、实际总结
MyBatis拦截器中还可以获取HttpServletRequest对象,笔者根据获取请求Header头缀中的token,从而获取部门编号和权限类型。然后拦截拼接sql语句完成数据权限过滤。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/17606.html