【mybatis系列】自定义实现拦截器插件Interceptor

【mybatis系列】自定义实现拦截器插件Interceptor目录类型规则介绍intercept(Invocationinvocation)plugin(Objecttarget)setProperties(Propertiesproperties)实战首先熟悉一下Mybatis的执行过程,如下图:拦截器应用场景:类型先说明Mybatis中可以被拦截的类型具体有以下四种:1.Executor:拦截执行器的方法。2.ParameterHandler:拦截参数的处理。3.ResultHandler:拦截结果集的处理。4.StatementHandl

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

首先熟悉一下Mybatis的执行过程,如下图:
在这里插入图片描述

类型

先说明Mybatis中可以被拦截的类型具体有以下四种:

1.Executor:拦截执行器的方法。
2.ParameterHandler:拦截参数的处理。
3.ResultHandler:拦截结果集的处理。
4.StatementHandler:拦截Sql语法构建的处理。

Jetbrains全家桶1年46,售后保障稳定

规则

Intercepts注解需要一个Signature(拦截点)参数数组。通过Signature来指定拦截哪个对象里面的哪个方法。@Intercepts注解定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts { 
   
    /** * 定义拦截点 * 只有符合拦截点的条件才会进入到拦截器 */
    Signature[] value();
}

Signature来指定咱们需要拦截那个类对象的哪个方法。定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ 
   })
public @interface Signature { 
   
  /** * 定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个 */
  Class<?> type();

  /** * 在定义拦截类的基础之上,在定义拦截的方法 */
  String method();

  /** * 在定义拦截方法的基础之上在定义拦截的方法对应的参数, * JAVA里面方法可能重载,故注意参数的类型和顺序 */
  Class<?>[] args();
}

标识拦截注解@Intercepts规则使用,简单实例如下:


@Intercepts({ 
   //注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器
        @Signature(
                type = ResultSetHandler.class,
                method = "handleResultSets", 
                args = { 
   Statement.class}),
        @Signature(type = Executor.class,
                method = "query",
                args = { 
   MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})

说明:
@Intercepts:标识该类是一个拦截器;
@Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法;
– type:上述四种类型中的一种;
– method:对应接口中的哪类方法(因为可能存在重载方法);
– args:对应哪一个方法的入参;

method中对应四种的类型的方法:

拦截类型 拦截方法
Executor update, query, flushStatements, commit, rollback,getTransaction, close, isClosed
ParameterHandler getParameterObject, setParameters
StatementHandler prepare, parameterize, batch, update, query
ResultSetHandler handleResultSets, handleOutputParameters

介绍

谈到自定义拦截器实践部分,主要按照以下三步:

  1. 实现org.apache.ibatis.plugin.Interceptor接口,重写以下方法:
public interface Interceptor { 
   
    Object intercept(Invocation var1) throws Throwable;
    Object plugin(Object var1);
    void setProperties(Properties var1);
}
  1. 添加拦截器注解@Intercepts{...}。具体值遵循上述规则设置。
  2. 配置文件中添加拦截器。

intercept(Invocation invocation)

从上面我们了解到interceptor能够拦截的四种类型对象,此处入参invocation便是指拦截到的对象。
举例说明:拦截**StatementHandler#query(Statement st,ResultHandler rh)**方法,那么Invocation就是该对象。

plugin(Object target)

这个方法的作用是就是让mybatis判断,是否要进行拦截,然后做出决定是否生成一个代理。

    @Override
    public Object plugin(Object target) { 
   
    //判断是否拦截这个类型对象(根据@Intercepts注解决定),然后决定是返回一个代理对象还是返回原对象。
//故我们在实现plugin方法时,要判断一下目标类型,如果是插件要拦截的对象时才执行Plugin.wrap方法,否则的话,直接返回目标本身。
        if (target instanceof StatementHandler) { 
   
            return Plugin.wrap(target, this);
        }
        return target;
    }

注意:每经过一个拦截器对象都会调用插件的plugin方法,也就是说,该方法会调用4次。根据@Intercepts注解来决定是否进行拦截处理。

setProperties(Properties properties)

拦截器需要一些变量对象,而且这个对象是支持可配置的。

实战

  • 自定义拦截器
@Intercepts(value = { 
   @Signature(type = StatementHandler.class, method = "prepare", args = { 
   Connection.class, Integer.class})})
public class MyInterceptor implements Interceptor { 
   

    @Override
    public Object intercept(Invocation invocation) throws Throwable { 
   
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        Object obj = boundSql.getParameterObject();
        String sql = boundSql.getSql();
        if (sql.trim().toUpperCase().startsWith("INSERT")) { 
   
            ReflectUtil.setFieldValue(obj, "rev", 0);
            ReflectUtil.setFieldValue(obj, "createTime", new Date());
            ReflectUtil.setFieldValue(obj, "operateTime", new Date());
            ReflectUtil.setFieldValue(boundSql,"parameterObject", obj);

        } else if (sql.trim().toUpperCase().startsWith("UPDATE")) { 
   
            sql = sql.replaceAll(" set ", " SET ")
                    .replaceAll(" Set ", " SET ")
                    .replaceAll(" SET ", " SET rev = rev+1, operate_time = NOW(), ");
            ReflectUtil.setFieldValue(boundSql,"sql", sql);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) { 
   
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) { 
   

    }
}

主要看下核心代码方法intercept():
这段代码主要目的:拦截insert和update语句,利用反射机制,设置insert语句的参数rev(版本号,利用乐观锁),第一次查询,故创建时间和操作时间相同;update是将版本号+1,统一修改其操作时间。

  • mybatis-config
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>    
	<plugins>
        <plugin interceptor="com.qxy.mybatis.interceptor.MyInterceptor"/>
    </plugins>

</configuration>
  • application.yml
    特别重要的一点,一定将mybatis-config中的对象注入到Sprint容器中,否则不会生效。
...//省略其他配置
mybatis:
  config-location: classpath:/mybatis-config.xml
  • ReflectUtil
public class ReflectUtil { 
   

    private ReflectUtil() { 
   }

    /** * 利用反射获取指定对象的指定属性 * @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, Object fieldValue) throws IllegalAccessException { 
   
        Field field = getField(obj, fieldName);
        if (field != null) { 
   
            field.setAccessible(true);
            field.set(obj, fieldValue);
        }
    }
}
  • debug
    在这里插入图片描述

上图中能够看到BoundSql对象中主要存储的属性值,所以我们自定义拦截器时,主要针对BoundSql的属性值进行修改。
程序代码没有走到我们反射机制设置值的位置,测试createTime=null;
在这里插入图片描述

返回之前,看下BoundSql对象的值,创建时间已被赋值。
在这里插入图片描述

源代码:https://github.com/stream-source

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/209969.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 初中python培训机构

    初中python培训机构都知道现在Python这门编程语言很火,那它究竟火到什么程度?可能互联网上铺天盖地的Python学习贴不够直观,求职平台上Python相关工资水涨船高,也离我们普通人太远,但——Python被纳入基础教育体系呢?浙江省八年级将新增Python编程课程风变编程得到最新消息,在2020年9月开始的新学期中,浙江省三年级到九年级信息技术课将同步替换新教材,而其中最大的变化是,八年级将新增Python课程内容。同时,新高一信息技术编程语言由VB替换为Python,大数据、人工智能、程序设计与算法按照教材规划

    2022年5月16日
    47
  • 在工厂做IT的职业前途[通俗易懂]

    在工厂做IT的职业前途[通俗易懂]从毕业到现在大部分时间是在制造工厂渡过.大学读的是信息管理,什么都学,什么都不懂.所以刚毕业时候工作很难找.几经周折,终于进了厦门的一家制造工厂的MIS部门做开发ERP的Coder.工厂规模虽不是很大但IT部门的学习氛围还可以,…

    2025年10月31日
    4
  • 同居第60天,在家族群里目睹了男友劈腿全过程……我笑抽了哈哈哈!

    文案改编自:新青年大院NEWYOUTH。这里有全网最奇葩的情感故事,这里有你想不到的憨批投稿,这里有你和你身边朋友的影子,也有你恨的牙痒痒的各种痴男怨女。总之,这是一个关注了就停不…

    2021年6月21日
    91
  • mshtml相关「建议收藏」

    牛人翻译的文章:http://blog.csdn.net/jiangsheng/article/details/3793

    2022年4月7日
    40
  • 该用什么工具开发C++

    该用什么工具开发C++

    2022年3月13日
    32
  • python字典排序方法「建议收藏」

    python字典排序方法「建议收藏」字典是“键-值对”的无序可变序列在实际运用中,对字典进行排序是一个比较常见的操作,主要用到了python内置函数sorted(),该函数可以对所有可迭代的对象进行排序操作。语法(python3):sorted(iterable,key=None,reverse=False)参数说明:iterable:可迭代对象,即可以用for循环进行迭代的对象;key:主要是用来进行比较的元素,只有一个参数,具体的函数参数取自于可迭代对象中,用来指定可迭代对象中的一个元素来进行排序;reverse:排序规

    2022年6月18日
    28

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号