在mubatis中提供了plugin插件的方法,实际上就是拦截器,比如分页插件也是基于此来实现的
1.在mybatis中拦截器可以针对4个点来进行拦截处理,也可以说是4个接口:
-
Excutor:拦截器执行器的相关方法,可以对这些方法进行增强处理
-
parameterHandler:拦截器参数处理,可以对参数进行统一的处理
-
resultSetHandle:拦截器结果集处理,可以对返回的结果做统一处理
-
Statement Handle:拦截sql构建的处理
执行与添加顺序
拦截器主要是拦截以上4个接口,对里面的方法进行增强处理,所以如果以方法来看拦截器的执行顺序是与这几个接口里面的方法在Mybatis执行流程里面的顺序有关,但如果以接口来看拦截器的执行顺序,大概流程是这样(不了解的可以去看看 Mybatis流程源码):
Executor→ParameterHandler→StatementHandler→ResultSetHandler
拦截器生效入口
拦截器是什么时候介入到执行流程中来的呢?
以上述4个接口为例,所以有4个介入入口,均是在执行流程中初始化的时候介入的,如下图:
Configuration内部:
二、使用
使用拦截器其实很简单,我们只需要打上两个注解,实现一个接口,并在配置文件中配置一下就可以了
要打上的两个注解:
@Intercepts: 该注解等于是个标识,标识该类是个拦截器,需要配合@Signature来使用
@Signature: 该注解也是个标识,表示要拦截的接口以及接口内的哪个方法,有三个参数
type :代表我们要拦截的是哪个接口(4个里面选一个)
method :代表我们要拦截的是接口里面的哪一个方法(从接口里面去选一个)
args :要拦截的方法里面的参数类型,方法里面有几个传参这里就要写几个(因为方法存在重载,名称还无法确定唯一性)
要实现的接口:
Interceptor,内部有三个方法,一个必须要实现,两个随意
-
intercept():必须要实现的方法,这里就可以处理我们的逻辑
-
plugin():可选择实现,返回目标对象或者代理对象
-
setProperties():获取参数,可以从外部获取一些配置参数
例子:
/**
* 这里我们拦截Executor里面的query和update方法
* 一个@Signature 代表要拦截的一个方法
*/
@Intercepts({
/**
* type :代表我们要拦截的是哪个接口(4个里面选一个)
* method :代表我们要拦截的是接口里面的哪一个方法(从接口里面去选一个)
* args :要拦截的方法里面的参数类型,方法里面有几个传参这里就要写几个(因为方法存在重载,名称还无法确定唯一性)
**/
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
),
@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)
})
public class DemoInterceptor1 implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//被代理对象
Object target = invocation.getTarget();
//代理方法
Method method = invocation.getMethod();
//方法参数
Object[] args = invocation.getArgs();
// do something ...方法拦截前执行代码块
System.out.println("方法拦截前执行 do something ...");
// 本方法执行(这就是执行被拦截的源方法)
Object result = invocation.proceed();
// do something ...方法拦截后执行代码块
System.out.println("方法拦截后执行 do something ...");
return result;
}
/**
* 通过该方法决定要返回的对象是目标对象还是对应的代理
* 一般就两种情况(乱来小心报错):
* 1. return target; 直接返回目标对象,相当于当前Interceptor没起作用,不会调用上面的intercept()方法
* 2. return Plugin.wrap(target, this); 返回代理对象,会调用上面的intercept()方法
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 用于获取在Configuration初始化当前的Interceptor时候设置的一些参数
*
* @param properties Properties参数
*/
@Override
public void setProperties(Properties properties) {
}
}
方法plugin中如果使用的是Plugin.warp(target,this)表示使用代理方式,即上面重写的interceptor会被代理生效,如果直接返回对象,即不使用重写的interceptor,默认mybatis自带。
三.创建mybatis执行SQL语句的日志打印
@Component
@Intercepts(
{@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, Result.class}),
@Signature(type = Executor.class,method = "insert",args = {MappedStatement.class,Object.class,Result.class}),
@Signature(type = Executor.class,method = "update",args = {MappedStatement.class,Object.class,Result.class}),
@Signature(type = Executor.class,method = "delete",args = {MappedStatement.class,Object.class,Result.class})
})
public class SqlExcutorLogsInterceptor implements Interceptor {
Logger logger = LoggerFactory.getLogger(SqlExcutorLogsInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0];
int one = 1;
//sql参数
Object paramter = invocation.getArgs()[one];
//sql mapper id
String id = mappedStatement.getId();
//sql
BoundSql boundSql = mappedStatement.getBoundSql(paramter);
//configuration
Configuration configuration = mappedStatement.getConfiguration();
//执行的sql语句,携带真实参数
String sql = sqlHandler(boundSql,configuration);
//sql执行的当前时间
long currentTime = System.currentTimeMillis();
//执行sql
invocation.proceed();
long endTime = System.currentTimeMillis();
//打印
logger.info(String.format("SQL_Mapper:%s",id));
logger.info(String.format("SQL:%s",sql));
logger.info(String.format("SQL执行时间:%s ms",endTime-currentTime));
return null;
}
/**
* @Author
* @Description sql语句里面的?替换成真实的参数
**/
private String sqlHandler(BoundSql boundSql, Configuration configuration){
// 获取mapper里面方法上的参数
Object sqlParameter = boundSql.getParameterObject();
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// sql原始语句(?还没有替换成我们具体的参数)
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (sqlParameter == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(sqlParameter.getClass())) {
value = sqlParameter;
} else {
MetaObject metaObject = configuration.newMetaObject(sqlParameter);
value = metaObject.getValue(propertyName);
}
sql = sql.replaceFirst("\\?", value.toString());
}
}
}
return sql;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
四.实现自定义注解生效
使用反射机制获取类字段,并获取字段上的注解,提取内容
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class,Object.class})})
public class GeneratedKeyInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(GeneratedKeyInterceptor.class);
/**
*单个插入名称
*/
private static final String INSERT = "insert";
/**
* 单个插入名称
*/
private static final String SAVE = "save";
/**
* 批量插入名称
*/
private static final String INSERT_BATCH = "insertBatch";
/**
* 批量插入名称
*/
private static final String SAVE_BATCH = "saveBatch";
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
//获取sql
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
//不是插入语句直接放行,不拦截
if (sqlCommandType != SqlCommandType.INSERT){
invocation.proceed();
}
//获取sql参数
Object paramter = invocation.getArgs()[1];
//获取数据库对象
BaseModel dbObj = findDbObj(paramter);
if (dbObj == null){
invocation.proceed();
}
//插入数据库
if (mappedStatement.getId().contains(INSERT) || mappedStatement.getId().contains(SAVE)){
generatedKey(dbObj);
}
return null;
}
/**
* 获取私有成员变量并设置主键
* @param dbObj
*/
public void generatedKey(Object dbObj) throws IllegalAccessException {
Field[] declaredFields = dbObj.getClass().getDeclaredFields();
for (Field field : declaredFields){
if (field.getType().isAssignableFrom(Long.class)){
break;
}
//反射获取注解内容
DistributedId annotation = field.getAnnotation(DistributedId.class);
if (annotation == null){
break;
}
//访问私有变量
field.setAccessible(true);
//已经设置了,退出
if (field.get(dbObj) != null){
break;
}
//自定义设置内容
field.set(dbObj,null);
}
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor){
//代理执行拦截器
return Plugin.wrap(target,this);
}else {
//不进行自定义拦截器处理
return target;
}
}
public BaseModel findDbObj(Object paramenter){
if (paramenter instanceof BaseModel){
return (BaseModel) paramenter;
}else if (paramenter instanceof Map){
for (Object val : ((Map<?,?>)paramenter).values()){
if (val instanceof BaseModel){
return (BaseModel) val;
}
}
}
return null;
}
}
BaseModel类
public class BaseModel implements Serializable {
/**
* 创建时间
*/
protected Date createTime;
/**
* 更新时间
*/
protected Date updateTime;
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "BaseModel{" + "createTime=" + createTime + ", updateTime=" + updateTime + '}';
}
}