最近花了三天空余时间,研究了一下sql拦截器重置sql中createTime和updateTime;
其实是看了好多文章,中途遇到了一点坎,最后实现了之后,发现代码也就一点点;
现在总结一下,以便深入理解
分享几个比较好的博客链接:
MyBatis 工作流程及插件开发
深入理解Mybatis插件开发
带有源码实现的解析
Mybatis 插件实现动态设置参数
与其称为Mybatis插件,不如叫Mybatis拦截器,更加符合其功能定位,实际上它就是一个拦截器,应用代理模式,在方法级别上进行拦截。
(1)拦截器的作用就是 在mybatis操作sql的同时,我们能够拦截一个节点,穿插我们想要的操作进去;
(2)mybatis的拦截器主要可以通过org.apache.ibatis.plugin 包下的 Interceptor 类
和 org.apache.ibatis.plugin 包下的 Plugin 类 去实现;
(1)实现Interceptor ,重写其中的三个方法:
(2)添加拦截器的注解;
(3)在mybatis的配置文件中添加拦截器;
也就是说,我们自定义一个拦截器,并且标识出是拦截那个类(Exeutor)的具体哪个方法(method和args,通过这俩个,当有重载的方式时候,可以根据参数固定定位到某一个方法); 当这个拦截器能够被识别的时候,在执行sql的过程中,就回去执行自定义的Interceptor中的实现;
Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
a:Intercepts 标识我的类是一个拦截器
b:Signature 则是指明我们的拦截器需要拦截哪一个接口的哪一个方法
type 对应四类接口中的某一个,比如是 Executor
method 对应接口中的哪类方法,比如 Executor 的 update 方法
args 对应接口中的哪一个方法,比如 Executor 中 query 因为重载原因,方法有多个,args 就是指明参数类型,从而确定是哪一个方法
Mybatis在创建拦截器代理时候会判断一次,当前这个类 MyInterceptor 到底需不需要生成一个代理进行拦截,如果需要拦截,就生成一个代理对象,这个代理就是一个 {@link Plugin},它实现了jdk的动态代理接口; 如果不需要代理,则直接返回目标对象本身;
Mybatis为什么会判断一次是否需要代理呢?
(1)当前的Interceptor上面的注解定义哪些接口需要拦截,
(2)判断当前目标对象是否有实现对应需要拦截的接口,
(3)如果没有则返回目标对象本身,
(4)如果有则返回一个代理对象。
而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,如果是通过代理对象执行的,则会调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。
所以接着我们来看一下该invoke方法的内容。
invoke方法的逻辑是:如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前拦截器的intercept方法。如果不需要拦截,则直接调用当前的方法。Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。
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的配置文件中注册自定义的拦截器