package com.dy.common.mybatis; import java.lang.reflect.InvocationTargetException; import java.text.DateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Properties; import lombok.extern.slf4j.Slf4j; 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; /** * 拦截执行SQL发生异常的场景,并将执行错误,执行SQL和参数打印出来 * 拦截Executor里面的query和update方法 */ @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 } ) }) @Slf4j public class PrintExceptionSqlInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 获取执行方法的MappedStatement参数 MappedStatement 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(); Object response; try { response = invocation.proceed(); } catch (Exception e) { // 输出SQL异常信息 log.error("SQL ErrorException:", e); log.info("SQL Parameters: {}", boundSql.getParameterObject()); log.info("Execute SqlId: {}", sqlId); log.info("Completely Execute SQL: {}", getFullSql(configuration, boundSql)); // 根据源异常类型进行返回 if (e instanceof InvocationTargetException) { throw new InvocationTargetException(e); } else if (e instanceof IllegalAccessException) { throw new IllegalAccessException(e.getMessage()); } else { throw new RuntimeException(e); } } return response; } /** * 通过该方法决定要返回的对象是目标对象还是对应的代理 * 不要想的太复杂,一般就两种情况: *
* 1. return target; 直接返回目标对象,相当于当前Interceptor没起作用,不会调用上面的intercept()方法
* 2. return Plugin.wrap(target, this); 返回代理对象,会调用上面的intercept()方法
*
* @param target 目标对象
* @return 目标对象或者代理对象
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 用于获取在Configuration初始化当前的Interceptor时时候设置的一些参数
*
* @param properties Properties参数
*/
@Override
public void setProperties(Properties properties) {
}
/**
* 转义正则特殊字符 ($()*+.[]?\^{}
* \\需要第一个替换,否则replace方法替换时会有逻辑bug
*/
private static String makeQueryStringAllRegExp(String str) {
if (str != null && !"".equals(str)) {
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 + "'";
} 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 != null ? makeQueryStringAllRegExp(value) : "";
}
/**
* 获取完整的执行SQL
*/
public static String getFullSql(Configuration configuration, BoundSql boundSql) {
try {
return parseAndExtractFullSql(configuration, boundSql);
} catch (Exception e) {
// 如果解析失败返回原始SQL
return boundSql.getSql();
}
}
/**
* 组装完整的sql语句并把对应的参数都代入到sql语句里面
*
* @param configuration Configuration
* @param boundSql BoundSql
* @return sql完整语句
*/
private static String parseAndExtractFullSql(Configuration configuration, BoundSql boundSql) {
// 获取mapper里面方法上的参数
Object sqlParameter = boundSql.getParameterObject();
// sql语句里面需要的参数
List