mybatis 之定义拦截器 控制台SQL的打印
类型
先说明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();
}
mybatis 之定义拦截器 控制台SQL的打印的更多相关文章
- springmvc自己定义拦截器
Spring MVC也能够使用拦截器对请求进行拦截处理,用户能够自己定义拦截器来实现特定的功能,自己定义的拦截器必须实现HandlerInterceptor接口. 直接看下样例: package co ...
- SpringMVC利用拦截器防止SQL注入
引言 随着互联网的发展,人们在享受互联网带来的便捷的服务的时候,也面临着个人的隐私泄漏的问题.小到一个拥有用户系统的小型论坛,大到各个大型的银行机构,互联网安全问题都显得格外重要.而这些网站的背后,则 ...
- struts2学习笔记(5)---自己定义拦截器
什么是拦截器? struts2中拦截器分为Struts2定义好的拦截器和自己定义的拦截器. 其作用是在一个Action运行之前进行拦截,在Action运行之后又增加某些操作. 实现原理 当请求一个Ac ...
- Struts2通过自己定义拦截器实现登录之后跳转到原页面
这个功能对用户体验来说是非常重要的.实现起来事实上非常easy. 拦截器的代码例如以下: package go.derek.advice; import go.derek.entity.User; i ...
- jquery.ajax与axios及定义拦截器
首先导入jquery和axios包 jquery.ajax function reg(){ var username = $("#username").val(); var pas ...
- Struct2_定义拦截器并使用注解方式作用在Action的方法中
一.目的:通过在方法上加注解控制哪些方法需要登陆后才能访问 二.方式:利用拦截器判断用户是否登陆 三.实现步骤 定义配置文件struts.xml添加节点 1 2 3 4 5 6 7 8 9 1 ...
- CXF实战之自己定义拦截器(五)
CXF已经内置了一些拦截器,这些拦截器大部分默认加入到拦截器链中,有些拦截器也能够手动加入,如手动加入CXF提供的日志拦截器.也能够自己定义拦截器.CXF中实现自己定义拦截器非常easy.仅仅要继承A ...
- struts自己定义拦截器--登录权限控制
说明:该自己定义的拦截器实现用户登录的权限控制. login.jsp--->LoginAction--重定向-->MainAction--->main.jsp 一.1.整体的步骤: ...
- MyBatis功能点二:MyBatis提供的拦截器平台
前面关于MyBatis功能点二plugin已经介绍了一些应用及其实现的底层代码,本文总结MyBatis提供的拦截器平台框架体系. 通过MyBatis功能点二:从责任链设计模式的角度理解插件实现技术 - ...
- Mybatis拦截器实现SQL性能监控
Mybatis拦截器只能拦截四类对象,分别为:Executor.ParameterHandler.StatementHandler.ResultSetHandler,而SQL数据库的操作都是从Exec ...
随机推荐
- slf4j、log4j2及logback使用
slf4j.log4j2及logback使用 1.问题来源 之前看过关于slf4j.log4j2及logback的介绍,slf4j是门面,log4j2及logback是具体实现,仅使用slf4j门面是 ...
- flood_it 方法记录
心路历程 根据题面的描述,我们面临的问题无非是,每次将色块更新成什么颜色.又因为是从左上角开始更新,所以我的有了第一个想法. 将左上角的色块命名为"原色块". 对于每个色块,定义4 ...
- spring boot使用swagger生成api接口文档
前言 在之前的文章中,使用mybatis-plus生成了对应的包,在此基础上,我们针对项目的api接口,添加swagger配置和注解,生成swagger接口文档 具体可以查看本站spring boot ...
- HTML+CSS基础知识(6)背景的设置、表格的设计、表单的设计和框架集
文章目录 1.背景 1.1 代码 1.2 测试结果 2.背景练习 2.1 代码 2.2 测试结果 3.表格 3.1 代码 3.2 测试结果 4.练习 4.1 代码 4.2 测试结果 5.表单 5.1 ...
- OpenAPI 接口幂等实现
OpenAPI 接口幂等实现 1.幂等性是啥? 进行一次接口调用与进行多次相同的接口调用都能得到与预期相符的结果. 通俗的讲,创建资源或更新资源的操作在多次调用后只生效一次. 2.什么情况会需要保证幂 ...
- 一、SQL介绍
Mysql 简单来说,数据库就是一个存储数据的仓库,它将数据按照特定的规律存储在磁盘上.为了方便用户组织和管理数据,其专门提供了数据库管理系统.通过数据库管理系统,用户可以有效的组织和管理存储在数据库 ...
- RDD(弹性分布式数据集)及常用算子
RDD(弹性分布式数据集)及常用算子 RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据 处理模型.代码中是一个抽象类,它代表一个 ...
- docker-compose编排启动LNMP
2-1.docker-compose启动php #生成php镜像 [root@rocky8 data]#vim /data/dockerfile/ubuntu_php/Dockerfile FRO ...
- 【单元测试】Junit 4(三)--Junit4断言
1.0 前言 断言(assertion)是一种在程序中的一阶逻辑(如:一个结果为真或假的逻辑判断式),目的为了表示与验证软件开发者预期的结果--当程序执行到断言的位置时,对应的断言应该为真.若断言 ...
- 学习Rust第一天 Rust语言特点
学习Rust之前,我觉得应该首先了解Rust语言的设计目的是什么?为什么会诞生这门语言?这门语言和其他的语言有什么不同. Rust语言的设计特点 高性能:rust拥有和C++相近的性能表现,所以在嵌入 ...