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;
|
}
|
|
/**
|
* 通过该方法决定要返回的对象是目标对象还是对应的代理
|
* 不要想的太复杂,一般就两种情况:
|
* <p>
|
* 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<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
|
// sql原始语句(?还没有替换成我们具体的参数)
|
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
|
if (!parameterMappings.isEmpty() && 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;
|
}
|
|
}
|