Mybatis插件开发--实现sql拦截器-重置sql中的参数

最近花了三天空余时间,研究了一下sql拦截器重置sql中createTime和updateTime;
其实是看了好多文章,中途遇到了一点坎,最后实现了之后,发现代码也就一点点;
现在总结一下,以便深入理解

分享几个比较好的博客链接:
MyBatis 工作流程及插件开发
深入理解Mybatis插件开发
带有源码实现的解析
Mybatis 插件实现动态设置参数

一、拦截器准备工作

1. 什么是mybatis插件

与其称为Mybatis插件,不如叫Mybatis拦截器,更加符合其功能定位,实际上它就是一个拦截器,应用代理模式,在方法级别上进行拦截。

2. 为什么要使用拦截器,主要扩展操作的是哪个类:

(1)拦截器的作用就是 在mybatis操作sql的同时,我们能够拦截一个节点,穿插我们想要的操作进去;
(2)mybatis的拦截器主要可以通过org.apache.ibatis.plugin 包下的 Interceptor 类
和 org.apache.ibatis.plugin 包下的 Plugin 类 去实现;

3. 自定义一个mybatis拦截器的几个步骤:

(1)实现Interceptor ,重写其中的三个方法:
(2)添加拦截器的注解;
(3)在mybatis的配置文件中添加拦截器;
也就是说,我们自定义一个拦截器,并且标识出是拦截那个类(Exeutor)的具体哪个方法(method和args,通过这俩个,当有重载的方式时候,可以根据参数固定定位到某一个方法); 当这个拦截器能够被识别的时候,在执行sql的过程中,就回去执行自定义的Interceptor中的实现;

4. mybatis能够拦截的四个类,以及拦截器的顺序:
Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
5.拦截器的注解 规则解析:

a:Intercepts 标识我的类是一个拦截器
b:Signature 则是指明我们的拦截器需要拦截哪一个接口的哪一个方法
    type 对应四类接口中的某一个,比如是 Executor
    method 对应接口中的哪类方法,比如 Executor 的 update 方法
    args 对应接口中的哪一个方法,比如 Executor 中 query 因为重载原因,方法有多个,args 就是指明参数类型,从而确定是哪一个方法

6.plugin方法的作用:
  • Mybatis在创建拦截器代理时候会判断一次,当前这个类 MyInterceptor 到底需不需要生成一个代理进行拦截,如果需要拦截,就生成一个代理对象,这个代理就是一个 {@link Plugin},它实现了jdk的动态代理接口; 如果不需要代理,则直接返回目标对象本身;

    Mybatis为什么会判断一次是否需要代理呢?

    • 默认情况下,Mybatis只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler
    • 通过 {@link Intercepts} 和 {@link Signature} 两个注解共同完成
    • 所以Mybatis会做一次判断,拦截点看看是不是这四个接口里面的方法,不是则不拦截,直接返回目标对象,如果是则需要生成一个代理
7. 添加到Mybatis配置文件中注册插件的plugins
<plugins>
     <plugin interceptor="com.zy.mybatisinterceptor.core.SqlInterceptor"></plugin>
</plugins>
8. mybatis拦截器的一个思想概述
(1)原理

(1)当前的Interceptor上面的注解定义哪些接口需要拦截,
(2)判断当前目标对象是否有实现对应需要拦截的接口,
(3)如果没有则返回目标对象本身,
(4)如果有则返回一个代理对象。
而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,如果是通过代理对象执行的,则会调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。
所以接着我们来看一下该invoke方法的内容。
    invoke方法的逻辑是:如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前拦截器的intercept方法。如果不需要拦截,则直接调用当前的方法。Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。

(2)解析一下invocation对象:

在这里插入图片描述

二、附上实现代码

userDo

import com.zy.mybatisinterceptor.core.CreateTime;
import com.zy.mybatisinterceptor.core.UpdateTime;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * 用户实体
 *
 * @Author zy
 * @Date 2019/7/9 15:44
 */
@Data
@Accessors(chain = true)
@NoArgsConstructor
public class UserDo implements Serializable {

    private int id;
    private String userName;
    private String sex;
    private int age;
    private String phoneNumber;
    private String address;
    private String pwd;
    private String email;
    @CreateTime
    private Date createTime;
    @UpdateTime
    private Date updateTime;
    private String pwdQuestion;
    private String pwdAnswer;

    private static final long serialVersionUID = 1L;

}

userMapper

public interface UserDoMapper {
    int insertUser(UserDo record);
}

定一两个关于时间的自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description: createTime自定义注解
 * @Author: zhangyu
 * @Date:Created in 16:48 2019/7/11
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface CreateTime {
    String value() default "";
}

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Description: updateTime自定义注解
 * @Author: zhangyu
 * @Date:Created in 16:49 2019/7/11
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface UpdateTime {
    String value() default "";
}

拦截器

**import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.Properties;


/**
 * @Description: 拦截器核心代码,拦截重写crateTime和updateTime
 * @Author: zy
 * @Date:Created in 9:37 2019/7/10
 */
@Component
@Slf4j
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
//        @Signature(type = Executor.class, method = "query", args = {Statement.class, ResultHandler.class})
})
public class SqlInterceptor implements Interceptor {
//
//    /**
//     * org.apache.ibatis.executor.Executor;
//     * 是 Mybatis 的内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过 ResultSetHandler 进行自动映射,另外,它还处理了二级缓存的操作。
//     * Executor创建StatementHandler对象,
//     * 同时,创建ParameterHandler和ResultSetHandler对象,而ParameterHandler和ResultSetHandler都依赖TypeHandler;
//     */
//    Executor;
//    /**
//     * org.apache.ibatis.executor.statement.StatementHandler;
//     * 是 Mybatis 直接和数据库执行 sql 脚本的对象,另外,它也实现了 Mybatis 的一级缓存。
//     */
//    StatementHandler;
//    /**
//     * org.apache.ibatis.executor.parameter.ParameterHandler;
//     *  是 Mybatis 实现 sql 入参设置的对象。
//     */
//    ParameterHandler;
//    /**
//     * org.apache.ibatis.executor.resultset.ResultSetHandler;
//     * 是 Mybatis 把 ResultSet 集合映射成 POJO 的接口对象。处理查询结果集;
//     */
//    ResultSetHandler;



    private static final Logger logger = LoggerFactory.getLogger(SqlInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        //获取sql命令操作类型
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        logger.info("获取的sql命令为:" + sqlCommandType);

        //获取参数
        Object parameter = invocation.getArgs()[1];
        if (null != parameter) {
            //获取成员变量
            Field[] declaredFields = parameter.getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                if (field.getAnnotation(CreateTime.class) != null) {
                    //insert语句,插入createTime
                    if (SqlCommandType.INSERT.equals(sqlCommandType)) {
                        field.setAccessible(true);
                        field.set(parameter, new Date());
                    }
                }
                if (field.getAnnotation(UpdateTime.class) != null) {
                    //insert或者update语句,插入updateTime
                    if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
                        field.setAccessible(true);
                        field.set(parameter, new Date());
                    }
                }
            }
        }
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {
    }

}
**

在mybatis的配置文件中注册自定义的拦截器

<?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>
    <settings>
        <!-- 当resultType为Map时,null值得属性也要put进去 -->
        <setting name="callSettersOnNulls" value="true"/>
        <!-- 开启驼峰匹配 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <plugins>
        <plugin interceptor="com.zy.mybatisinterceptor.core.SqlInterceptor"></plugin>
    </plugins>
</configuration>
  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值