mybatis拦截器及项目应用

目录

拦截器接口
注册拦截器
拦截原理
简单打印的demo
项目中编写的权限拦截器


拦截器接口

接口中的方法

Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:

import java.util.Properties;
 
public interface Interceptor {
  //当plugin函数返回代理,就可以对其中的方法进行拦截来调用intercept方法
  Object intercept(Invocation invocation) throws Throwable;
  //plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。
  Object plugin(Object target);
 //在Mybatis配置文件中指定一些属性
  void setProperties(Properties properties);
 
}

plugin方法中我们可以决定是否要进行拦截。
intercept方法就是要进行拦截的时候要执行的方法。

Mybatis中SqlSession下的四大核心组件:ParameterHandler 、ResultSetHandler 、StatementHandler 、Executor 。Mapper执行的过程也是这四个组件来完成的。他们包含的方法如下:
Executor
(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler
(getParameterObject, setParameters)
StatementHandler
(prepare, parameterize, batch, update, query)
ResultSetHandler
(handleResultSets, handleOutputParameters)

plugin方法参数可以接收到 这四个核心组件,通常拦截StatementHandler 、Executor。
拦截 return Plugin.wrap(target, this);
不拦截 return target;

intercept方法
最后要加return invocation.proceed();
继续执行

实现接口的类的重要注解

@Intercepts用于表明当前的对象是一个Interceptor,
而@Signature则表明要拦截的接口、方法以及对应的参数类型。

@Intercepts( {       
@Signature(method = "query", type = Executor.class, args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }),      
@Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })

第一个@Signature我们定义了该Interceptor将拦截Executor接口中参数类型为MappedStatement、Object、RowBounds和ResultHandler的query方法;
第二个@Signature我们定义了该Interceptor将拦截StatementHandler中参数类型为Connection的prepare方法。

注册拦截器

mybatis配置文件中

<configuration>
    <plugins>
       <plugin interceptor="com.tiantian.mybatis.interceptor.MyInterceptor">
           <property name="prop1" value="prop1"/>
       </plugin>
    </plugins>

拦截原理

mybatis执行sql过程 产生sql语句->产生statement->执行sql语句

在产生statement过程中可以拦截。
由于Statement语句是通过RoutingStatementHandler对象的prepare方法生成的。所以,拦截StatementHandler接口的prepare方法就可以更改sql语句。因为包括sql等其他属性在内的多个属性对外部都是封闭的,是对象的私有属性,所以要引入反射机制来获取或者更改对象的私有属性。

sqlsession四大接口对象介绍

Executor(接口)
它是一个执行器,真正进行java与数据库交互的对象,实际干活的。
StatementHandler(接口)
它是语句处理器,处理数据库会话的。
ParameterHandler:
它是对预编译语句进行参数的设置,完成对预编译参数的设置。
ResultSetHandler
返回结果,改变率很低。

四大对象的调用关系

Executor先调用StatementHandler里prepa方法预编译SQL语句,并设置参数,然后再用parameterize方法来使用ParameterHandler设置参数,完成预编译,执行查询的话,使用ResultHandler将结果返回给调用者,其他操作也类似。

简单打印的demo

import java.sql.Connection;
import java.util.Properties;
 
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
 
@Intercepts( {
       @Signature(method = "query", type = Executor.class, args = {
              MappedStatement.class, Object.class, RowBounds.class,
              ResultHandler.class }),
       @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class MyInterceptor implements Interceptor {
 
    public Object intercept(Invocation invocation) throws Throwable {
       Object result = invocation.proceed();
       System.out.println("Invocation.proceed()");
       return result;
    }
 
    public Object plugin(Object target) {
       return Plugin.wrap(target, this);
    }
 
    public void setProperties(Properties properties) {
       String prop1 = properties.getProperty("prop1");
       String prop2 = properties.getProperty("prop2");
       System.out.println(prop1 + "------" + prop2);
    }
 
}

//来源:https://blog.csdn.net/moshenglv/article/details/52699976

mybatis配置文件中注册后
即可在每次运行查询时观察到打印语句

项目中编写的权限拦截器

  1. mybatis拦截器常用于分页器,网上大多代码也是讲的分页器。我用的框架中已自带分页器,所以不再需要自己编写.
  2. 在mybatis配置文件中注册时发现,原本以为分页器最后执行应该最后注册,实际上却发现越后执行的拦截器就要放在越上面,这个拦截器我放在了最下面。
  3. 网上常说的拦截RoutingStatementHandler ,不知道为何,分页器和此拦截器有一个拦截RoutingStatementHandler 后,另一个就拦截不到了,因此此拦截器直接拦截的StatementHandler。

此拦截器根据查询参数的Map中是否包含 permission参数 来决定是否拦截select函数,从而进行页面展示的拦截。

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.loushang.bsp.security.author.intercept.web.FilterInvocation;
import org.loushang.bsp.security.session.ISessionStore;
import org.loushang.bsp.security.session.SessionStoreFactory;
import org.loushang.bsp.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;

@Intercepts({@org.apache.ibatis.plugin.Signature(type=org.apache.ibatis.executor.statement.StatementHandler.class, method="prepare", args={Connection.class})})
public class WebSqlInterceptor implements Interceptor {
 
    
    public Object intercept(Invocation invocation) throws Throwable {
        
         if(invocation.getTarget() instanceof StatementHandler) {
             StatementHandler delegate = (StatementHandler)invocation.getTarget();
              BoundSql boundSql = delegate.getBoundSql();
              Object obj = boundSql.getParameterObject();
//              if (obj instanceof Permission<?>) {
//                Permission<?> per = (Permission<?>) obj;
////                  
////                  MetaObject metaStatementHandler = SystemMetaObject.forObject(delegate);
////                  // 分离代理对象链(由于目标类可能被多个插件拦截,从而形成多次代理,通过下面的两次循环
////                  // 可以分离出最原始的的目标类)
////                  while (metaStatementHandler.hasGetter("h")) {
////                      Object object = metaStatementHandler.getValue("h");
////                      metaStatementHandler = SystemMetaObject.forObject(object);
////                  }
////                  // 分离最后一个代理对象的目标类
////                  while (metaStatementHandler.hasGetter("target")) {
////                      Object object = metaStatementHandler.getValue("target");
////                      metaStatementHandler = SystemMetaObject.forObject(object);
////                  }
////                  MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
////                  boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
//                  
//                MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
//                  //拦截到的prepare方法参数是一个Connection对象
//                  Connection connection = (Connection)invocation.getArgs()[0];
//                  //获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
//                  String sql = boundSql.getSql();
//                  System.out.println("成功拦截Permission sql:"+sql);
//                  String filterWebSql=null;
//                  if("web"==per.getPermissionType()) {
//                    filterWebSql =permissionGetWebSql(sql,per) ;
//                  }else if("task"==per.getPermissionType()){
//                    filterWebSql =permissionGetTaskSql(sql,per) ;
//                  }else {
//                    filterWebSql = sql;
//                  }
//                  //利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
////                  ReflectUtil.setFieldValue(boundSql, "sql", filterWebSql);
//              }else {
                  
                  MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
                  //拦截到的prepare方法参数是一个Connection对象
                  String sql = boundSql.getSql();
                      Connection connection = (Connection)invocation.getArgs()[0];
//                    System.out.println("成功拦截select sql:"+sql); 
                      Object parameterObject = boundSql.getParameterObject();
                      if (((parameterObject instanceof Map)) && 
                        (((Map)parameterObject).containsKey("permission"))) {
                          Map paraMap = (Map)parameterObject;
//                        System.out.println("成功拦截参数Map包含permission的 且value:"+paraMap.get("permission"));
                          if ((paraMap.get("permission") instanceof String)) {
                              String filterWebSql=sql;
                              String userId = (String) paraMap.get("userId");
                              StringBuffer webcondition = new StringBuffer().append("WEBSITE_ID in (select WEBSITE_ID from pub_user_webrole WHERE USER_ID=\"").append(userId).append("\" and ROLE_TYPE=\"1\")");
                              StringBuffer taskcondition = new StringBuffer().append("tab.TASK_ID in (select TASK_ID from pub_user_taskrole WHERE USER_ID=\"").append(userId).append("\" and ROLE_TYPE=\"1\")");
                              if("web"==paraMap.get("permission")&&(!"SUPERADMIN".equals(userId))) {
                                  System.out.println("拦截器过滤permission为web的sql");
                                  StringBuffer newsql = new StringBuffer();
                                  newsql.append("select tab.* from(").append(sql).append(") tab where ").append(webcondition);
                                  filterWebSql = new String(newsql);
                              }
                              else if("task"==paraMap.get("permission")&&(!"SUPERADMIN".equals(userId))){
                                  System.out.println("拦截器过滤permission为task的sql");
                                  StringBuffer newsql = new StringBuffer();
                                  /**
                                   * 若存在只有taskId没有wensiteId的查询 之后要编写根据taskId查询websiteId的语句。
                                   */
                                  //下面的是任务权限与网站权限混合
                                  //newsql.append("select tab.* from(").append(sql).append(") tab left join crawler_task tab on tab1.TASK_ID = tab2.TASK_ID where ").append(webcondition).append(" and ").append(taskcondition);
                                  newsql.append("select tab.* from(").append(sql).append(") tab where ").append(taskcondition);
                                  filterWebSql = new String(newsql);
                              }
                              //利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
                              ReflectUtil.setFieldValue(boundSql, "sql", filterWebSql);
                            }
                      }
              }
       return invocation.proceed();
    }
 
    //由于分页拦截器只分页传入为Map类型的 传入对象会使分页器失效 所以放弃使用Permission 以下两个函数均未用到
    private String permissionGetWebSql(String sql, Permission<?> per) {
        // TODO Auto-generated method stub
        String userId=per.getUserId();
        StringBuffer newsql = new StringBuffer();
        newsql.append("select * from(").append(sql).append(") tab where WEBSITE_ID in (select WEBSITE_ID from pub_user_webrole WHERE USER_ID=\"").append(userId).append("\"and ROLE_TYPE=\"1\")");
        return new String(newsql);
    }

    private String permissionGetTaskSql(String sql, Permission<?> per) {
        String userId=per.getUserId();
        StringBuffer newsql = new StringBuffer();
//      newsql.append("select * from(").append(sql).append(") tab where WEBSITE_ID in (select WEBSITE_ID from pub_user_webrole WHERE USER_ID=\"").append(userId).append("\"and ROLE_TYPE=\"1\")");
        return sql;
    }

    public Object plugin(Object target) {
        if(target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }else {
            return target;
        }
    }
 
    public void setProperties(Properties properties) {
       String prop1 = properties.getProperty("prop1");
       String prop2 = properties.getProperty("prop2");
       System.out.println(prop1 + "------" + prop2);
    }
    
    
    /**
     * 利用反射进行操作的一个工具类
     *
     */
    private static class ReflectUtil {
       /**
        * 利用反射获取指定对象的指定属性
        * @param obj 目标对象
        * @param fieldName 目标属性
        * @return 目标属性的值
        */
       public static Object getFieldValue(Object obj, String fieldName) {
           Object result = null;
           Field field = ReflectUtil.getField(obj, fieldName);
           if (field != null) {
              field.setAccessible(true);
              try {
                  result = field.get(obj);
              } catch (IllegalArgumentException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
           }
           return result;
       }
      
       /**
        * 利用反射获取指定对象里面的指定属性
        * @param obj 目标对象
        * @param fieldName 目标属性
        * @return 目标字段
        */
       private static Field getField(Object obj, String fieldName) {
           Field field = null;
          for (Class<?> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
              try {
                  field = clazz.getDeclaredField(fieldName);
                  break;
              } catch (NoSuchFieldException e) {
                  //这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。
              }
           }
           return field;
       }
 
       /**
        * 利用反射设置指定对象的指定属性为指定的值
        * @param obj 目标对象
        * @param fieldName 目标属性
         * @param fieldValue 目标值
        */
       public static void setFieldValue(Object obj, String fieldName,
              String fieldValue) {
           Field field = ReflectUtil.getField(obj, fieldName);
           if (field != null) {
              try {
                  field.setAccessible(true);
                  field.set(obj, fieldValue);
              } catch (IllegalArgumentException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
           }
        }
    }
 
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容