一 mybatis的拦截器
1.1 拦截器介绍
拦截器是一种基于 AOP(面向切面编程)的技术,它可以在目标对象的方法执行前后插入自定义的逻辑。
1.2 语法介绍
1.注解@Intercepts
@Intercepts({@Signature(type = StatementHandler.class, method = “prepare”, args = {Connection.class, Integer.class})})
,表示在 SQL 执行之前进行拦截处理。
@Intercepts 的作用:声明这是一个拦截器。
属性:Signature(注解)
@Signature:要拦截的具体方法
属性: type-拦截接口(四种类型 ),
method-拦截的方法(update,insert,select),
args-重载时根据参数列表确定要拦截的方法。
2.介绍:
Executor:拦截执行器的方法,例如 update、query、commit、rollback 等。可以用来实现缓存、事务、分页等功能。
ParameterHandler:拦截参数处理器的方法,例如 setParameters 等。可以用来转换或加密参数等功能。
ResultSetHandler:拦截结果集处理器的方法,例如 handleResultSets、handleOutputParameters 等。可以用来转换或过滤结果集等功能。
StatementHandler:拦截语句处理器的方法,例如 prepare、parameterize、batch、update、query 等。可以用来修改 SQL 语句、添加参数、记录日志等功能。
1.3 API接口
1.intercept:主要是写我们具体业务逻辑,比如针对增删改sql语句添加更新日期。
2.plugin:生成代理对象
3.setProperties:设置拦截器属性
二 实现案例
2.1 结构
2.2 代码
package com.ljf.springboot.mybaits.demos.utils;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.*;
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.springframework.stereotype.Component;import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;/*** 1. 打印mysql完整的执行语句* 2. 打印mysql语句执行时间* 这里我们拦截Executor里面的query和update方法*/
@Component
@Intercepts({@Signature(method = "query",type = Executor.class,args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class,method = "update",args = {MappedStatement.class, Object.class})
})
public class LogInterceptor implements Interceptor {/*** 是否显示语句的执行时间*/public static final String PROPERTIES_KEY_ENABLE_EXECUTOR_TIME = "enableExecutorTIme";public static final String ENABLE_EXECUTOR_TIME = "0"; // 显示private boolean enableExecutorTime = false;@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("dddddd======");// 获取执行方法的MappedStatement参数,不管是Executor的query方法还是update方法,第一个参数都是MappedStatementMappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = null;if (invocation.getArgs().length > 1) {parameter = invocation.getArgs()[1];}String sqlId = mappedStatement.getId();BoundSql boundSql = mappedStatement.getBoundSql(parameter);Configuration configuration = mappedStatement.getConfiguration();long sqlStartTime = System.currentTimeMillis();Object re = invocation.proceed();long sqlEndTime = System.currentTimeMillis();// 打印mysql执行语句String sql = getSql(configuration, boundSql, sqlId);System.out.println(sql);// 打印mysql执行时间if (enableExecutorTime) {String sqlTimeLog = sqlId + " 方法对应sql执行时间:" + (sqlEndTime - sqlStartTime) + " ms";System.out.println(sqlTimeLog);}return re;}/*** 通过该方法决定要返回的对象是目标对象还是对应的代理* 不要想的太复杂,一般就两种情况:* <p>* 1. return target; 直接返回目标对象,相当于当前Interceptor没起作用,不会调用上面的intercept()方法* 2. return Plugin.wrap(target, this); 返回代理对象,会调用上面的intercept()方法** @param target 目标对象* @return 目标对象或者代理对象*/@Overridepublic Object plugin(Object target) {System.out.println("==================dssssssssss");return Plugin.wrap(target, this);}/*** 用于获取在Configuration初始化当前的Interceptor时时候设置的一些参数** @param properties Properties参数*/@Overridepublic void setProperties(Properties properties) {if (properties != null) {String executorTImeValue = properties.getProperty(PROPERTIES_KEY_ENABLE_EXECUTOR_TIME);if (executorTImeValue != null) {enableExecutorTime = executorTImeValue.equals(ENABLE_EXECUTOR_TIME);}}}private static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {return sqlId + " 方法对应sql执行语句:" + assembleSql(configuration, boundSql);}/*** 转义正则特殊字符 ($()*+.[]?\^{}* \\需要第一个替换,否则replace方法替换时会有逻辑bug*/private static String makeQueryStringAllRegExp(String str) {if (str != null && !str.equals("")) {return str.replace("\\", "\\\\").replace("*", "\\*").replace("+", "\\+").replace("|", "\\|").replace("{", "\\{").replace("}", "\\}").replace("(", "\\(").replace(")", "\\)").replace("^", "\\^").replace("$", "\\$").replace("[", "\\[").replace("]", "\\]").replace("?", "\\?").replace(",", "\\,").replace(".", "\\.").replace("&", "\\&");}return str;}/*** 获取参数对应的string值** @param obj 参数对应的值* @return string*/private static String getParameterValue(Object obj) {String value;if (obj instanceof String) {value = "'" + obj.toString() + "'";} else if (obj instanceof Date) {DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);value = "'" + formatter.format(new Date()) + "'";} else {if (obj != null) {value = obj.toString();} else {value = "";}}// 对特殊字符进行转义,方便之后处理替换return value != null ? makeQueryStringAllRegExp(value) : value;}/*** 组装完整的sql语句 -- 把对应的参数都代入到sql语句里面** @param configuration Configuration* @param boundSql BoundSql* @return sql完整语句*/private static String assembleSql(Configuration configuration, BoundSql boundSql) {// 获取mapper里面方法上的参数Object sqlParameter = boundSql.getParameterObject();// sql语句里面需要的参数 -- 真实需要用到的参数 因为sqlParameter里面的每个参数不一定都会用到List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();// sql原始语句(?还没有替换成我们具体的参数)String sql = boundSql.getSql().replaceAll("[\\s]+", " ");if (parameterMappings.size() > 0 && sqlParameter != null) {// sql语句里面的?替换成真实的参数TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();if (typeHandlerRegistry.hasTypeHandler(sqlParameter.getClass())) {sql = sql.replaceFirst("\\?", getParameterValue(sqlParameter));} else {MetaObject metaObject = configuration.newMetaObject(sqlParameter);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;}
}
2.3 验证效果
1.请求
2.日志结果