首页 > 数据库 >mybatis 之定义拦截器 控制台SQL的打印

mybatis 之定义拦截器 控制台SQL的打印

时间:2022-12-26 16:36:57浏览次数:35  
标签:拦截器 SQL Object sql mybatis import 拦截 class

类型

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

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

规则

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

 

介绍

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

实现org.apache.ibatis.plugin.Interceptor接口,重写以下方法:

public interface Interceptor {
 Object intercept(Invocation var1) throws Throwable;
 Object plugin(Object var1);
 void setProperties(Properties var1);
}

添加拦截器注解@Intercepts{...}。具体值遵循上述规则设置。

配置文件中添加拦截器。

 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


------------
拦截器

package com.bs.it.sp.common.Interceptor;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
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.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

@Intercepts({@Signature(type = Executor.class,method = "update",args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class,method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})})
public class MybatisLogInterceptor implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(MybatisLogInterceptor.class);

private Properties properties;


public Object intercept(Invocation invocation) throws Throwable {

  long start = 0L;
  String sqlId = "";
  BoundSql boundSql = null;
  Configuration configuration = null;
  Object returnValue = null;

  try {
    MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
    sqlId = mappedStatement.getId();
    if(sqlId.contains("History") || sqlId.contains("Tmp")){
      return invocation.proceed();
    }
  Object parameter = null;
  if (invocation.getArgs().length > 1) {
    parameter = invocation.getArgs()[1];
  }
  boundSql = mappedStatement.getBoundSql(parameter);
  configuration = mappedStatement.getConfiguration();
  start = System.currentTimeMillis();
  } catch (Exception e) {
  logger.debug("Mybatis拦截器前置处理异常 原因:", e);
  logger.error("Mybatis拦截器前置处理异常 原因:" + e);
}

  returnValue = invocation.proceed();

  try {
    long end = System.currentTimeMillis();
    long time = (end - start);
    String sql = getSql(configuration, boundSql, sqlId, time);
    logger.info("执行的sql 信息为={}",sql);
    System.out.println("当前执行的sql语句为:---------------------\n"+sql);
  } catch (Exception e) {
    logger.debug("Mybatis拦截器后置处理异常 原因:", e);
  logger.error("Mybatis拦截器后置处理异常 原因:" + e);
  }

  return returnValue;
}

public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
String sql = showSql(configuration, boundSql);
StringBuilder str = new StringBuilder(100);
str.append("【sqlId】").append(sqlId);
str.append("【SQL耗时-").append(time).append("-毫秒】");
str.append("【SQL】").append(sql);
//logger.debug(SQLFormatter.format(str.toString()));
return str.toString();
}

private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
value = value.replaceAll("\\\\", "\\\\\\\\");
value = value.replaceAll("\\$", "\\\\\\$");
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(obj) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}

}
return value;
}

public static String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));

} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
}

public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

public void setProperties(Properties properties0) {
this.properties = properties0;
}
}

将拦截器注入到spring容器中

@Bean
public MybatisLogInterceptor setInterceptor() {

  return new MybatisLogInterceptor();
}

标签:拦截器,SQL,Object,sql,mybatis,import,拦截,class
From: https://www.cnblogs.com/niCong/p/17006105.html

相关文章

  • apscheduler mysql 持久化任务
    apschedulermysql持久化任务1、下载第三方包这里使用pymysql连接mysqlpipinstallapschedulerpipinstallpymysqlpipinstallsqlalchemy2、直接参考代码imp......
  • mysql实现limit分页
    1.背景:背景1:查询返回的记录太多了,查看起来很不方便,怎么样能够实现分页查询呢?背景2:表里有4条数据,如果只想要显示第2、3条数据怎么办呢?MySQL中使用LIMIT实现分页......
  • mybatis中的土鸡杂鱼
    mybatis中的土鸡杂鱼目录mybatis中的土鸡杂鱼1、mapper接口为什么要和mapper.xml在同一个路径下?2、主键生成为什么配置一个字段就可以?原理3、为什么默认使用的是预编译Pre......
  • 我对《Mysql死锁排查:insert on duplicate死锁一次排查分析过程 - 少说点话 - 博客园》
    原文在这里:Mysql死锁排查:insertonduplicate死锁一次排查分析过程 比较菜,看了一遍还是不懂死锁是怎么形成的。绕了很多圈才全理解。特此记录。 关于mysql版本我先......
  • SQL Command Types All In One
    SQLCommandTypesAllInOne(......
  • MySql在ONLY_FULL_GROUP_BY模式下分组查询报错问题的解决
    MySQL5.7.5及以上版本在进行groupby查询报错:ORDERBYclauseisnotinGROUPBYclauseandcontainsnonaggregatedcolumn或SELECTlistisnotinGROUPBYclause......
  • MySQL索引
    1.索引是什么索引是一个单独的、存储在磁盘上的数据库结构,包含着对数据表里所有记录的引用指针。简单来讲,数据库索引就像是书前面的目录,能加快数据库的查询速度。(1)索引......
  • SQL(及存储过程)跑得太慢怎么办?
    上之前有给大家玩过一下SPL,这一次继续深入玩一下。​​ClickHouse挺快,esProcSPL更快_小目标青年的博客-SQL作为目前最常用的数据处理语言,广泛应用于查询、跑批等场景。......
  • 数据库悲观锁和乐观锁使用Mybatis
    一下是转载的oracle和Mysql两种数据库悲观锁和乐观锁机制及乐观锁实现方式:一、OracleOracle数据库悲观锁与乐观锁是本文我们主要要介绍的内容。有时候为了得到最大的性能,一......
  • EF Core如创建sqlite表
    提问EFCore如创建sqlite表回答在项目根目录执行命令dotnettoolinstall--globaldotnet-efdotnetaddpackageMicrosoft.EntityFrameworkCore.Designdotnetefm......