通过spring的AOP的切面,拦截到持久层执行的sql及参数
Posted Peter-OK
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过spring的AOP的切面,拦截到持久层执行的sql及参数相关的知识,希望对你有一定的参考价值。
声明:文章内容是 自己使用后整理,大部分工具代码出自大牛,但因无法确认出处,故仅在此处由衷的对无私分享源代码的作者表示感谢与致敬!
使用该方式需要按规范书写所有的mapper方法,否则会报错!
方式一:形参为自定义实体类
void insert(User user);方式二:形参为Map
void insert(Map<String,Object> map);方式三:形参为list
void batchInsertUser(@Param(value = "list") List<UserDTO> list);
方式四:形参为单个或多个参数,需要使用@Param注解
void insert(@Param("userName") String userName, @Param("age")Integer age);
LogSqlAnnotation.java
package com.xxx.xxx.xxx.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogSqlAnnotation
String value() default "";
LogSqlAspect.java
package com.xxx.xxx.xxx.aspect;
import com.xxx.xxx.xxx.util.Func;
import com.xxx.xxx.xxx.util.SqlUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Slf4j
//@Aspect
//@Component
public class LogSqlAspect
@Autowired
private SqlSessionFactory sqlSessionFactory;
/**
* 切点 扫描整个mapper
*
* @Pointcut("execution(* com.xxx.xxx.xxx.controller.*.*(..)) && @annotation(com.xxx.xxx.xxx.aspect.LogSqlAnnotation)")
* @Pointcut("execution(* com.xxx.xxx.xxx.mappers.*.*(..))")
*/
@Pointcut("execution(* com.xxx.xxx.xxx.mapper.*.*(..)) && !execution(* com.xxx.xxx.xxx.mapper.JobLogSqlMapper.*(..))")
public void logRecord()
// 定义
/**
* 前置通知
*
* @param jp
*/
@Before(value = "logRecord()")
public void before(JoinPoint jp)
String name = jp.getSignature().getName();
log.info(name + "方法开始执行");
/**
* 后置通知
*
* @param jp
*/
@After(value = "logRecord()")
public void after(JoinPoint jp)
String name = jp.getSignature().getName();
log.info(name + "方法执行结束...");
/**
* 返回通知
*
* @param jp
* @param result
*/
@AfterReturning(value = "logRecord()", returning = "result")
public void afterReturning(JoinPoint jp, Object result)
String name = jp.getSignature().getName();
log.info(name + "方法返回值为:" + result);
/**
* 异常通知
*
* @param jp
* @param e
*/
@AfterThrowing(value = "logRecord()", throwing = "e")
public void afterThrowing(JoinPoint jp, Exception e)
String name = jp.getSignature().getName();
log.info(name + "方法异常:" + e.getMessage());
// @Around(value = "logRecord()")
// public Object around(ProceedingJoinPoint pjp) throws Throwable
// return pjp.proceed();
//
@Around(value = "logRecord()")
public Object around(ProceedingJoinPoint pjp) throws Throwable
String sql = SqlUtils.getMybatisSql(pjp, sqlSessionFactory);
String dml= Func.getSqlDML(sql);
String remark= Func.analyzeSqlWhere(sql);
// 获取请求信息
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String requestUrl=null;
if(requestAttributes!=null)
HttpServletRequest request = requestAttributes.getRequest();
requestUrl = request.getRequestURL().toString();
Signature signature = pjp.getSignature();
String methodName = signature.getName();
String methodUrl = signature.toShortString();
log.info("记录执行sql--->dml:",dml);
log.info("记录执行sql--->remark:",remark);
if(remark!=null || (remark==null && dml != null && !"SELECT".equals(dml) ))
log.info("记录执行sql--->sql:",sql);
log.info("记录执行sql--->methodName:",methodName);
log.info("记录执行sql--->methodUrl:",methodUrl);
log.info("记录执行sql--->requestUrl:",requestUrl);
/*//<editor-fold desc="1、jdbc 保存日志">
Connection connection = JdbcUtils.getConnection();
Statement statement = connection.createStatement();
String insertSql = "insert into logsql(log_id, sql_str, method_name, method_url, request_url, create_date) " +
"values (" + "\\'" + logId + "\\'" + ","
+ "\\"" + sql + "\\"" + ","
+ "\\"" + methodName + "\\"" + ","
+ "\\'" + methodUrl + "\\'" + ","
+ "\\'" + requestUrl + "\\'" + ","
+ "NOW()" + ")";
statement.executeUpdate(insertSql);
//</editor-fold>*/
/*//<editor-fold desc="2、mybatis 通过model方式保存">
LogSql logSql = new LogSql();
logSql.setLogId(logId);
logSql.setMethodName(methodName);
logSql.setMethodUrl(methodUrl);
logSql.setSqlStr(sql);
logSql.setRequestUrl(requestUrl);
logSqlMapper.saveLogByModel(logSql);
//</editor-fold>*/
// 3、mybatis 通过 map方式保存"
// Map<String, String> map = new HashMap<>(16);
// map.put("sql_str", sql);
// map.put("method_name", methodName);
// map.put("method_url", methodUrl);
// map.put("request_url", requestUrl);
// logSqlMapper.saveLogByMap(map);
return pjp.proceed();
SqlUtils.java
package com.xxx.xxx.xxx.util;
import com.google.common.collect.Lists;
import com.xxx.xxx.xxx.model.log.LogSqlConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.text.DateFormat;
import java.time.Instant;
import java.util.*;
/**
* @date 2021/9/20 10:16
* @description 获取aop中的SQL语句,借鉴来源https://blog.csdn.net/sdzhangshulong/article/details/104393244
*/
@Slf4j
public class SqlUtils
/**
* 获取aop中的SQL语句
*
* @param pjp
* @param sqlSessionFactory
* @return
* @throws IllegalAccessException
*/
public static String getMybatisSql(ProceedingJoinPoint pjp, SqlSessionFactory sqlSessionFactory) throws IllegalAccessException
Map<String, Object> map = new HashMap<>(16);
//1.获取namespace+methdoName
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
String namespace = method.getDeclaringClass().getName();
String methodName = method.getName();
//2.根据namespace+methdoName获取相对应的MappedStatement
Configuration configuration = sqlSessionFactory.getConfiguration();
MappedStatement mappedStatement = configuration.getMappedStatement(namespace + "." + methodName);
//3.获取方法参数列表名
Parameter[] parameters = method.getParameters();
//4.形参和实参的映射,获取实参
Object[] objects = pjp.getArgs();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++)
Object object = objects[i];
//说明该参数没有注解,此时该参数可能是实体类,也可能是Map,也可能只是单参数
if (parameterAnnotations[i].length == 0)
if (object.getClass().getClassLoader() == null && object instanceof Map)
map.putAll((Map<? extends String, ?>) object);
log.info("该对象为Map");
else //形参为自定义实体类
map.putAll(objectToMap(object));
log.info("该对象为用户自定义的对象");
else //说明该参数有注解,且必须为@Param
for (Annotation annotation : parameterAnnotations[i])
if (annotation instanceof Param)
map.put(((Param) annotation).value(), object);
//5.获取boundSql
BoundSql boundSql = mappedStatement.getBoundSql(map);
// BoundSql boundSql = mappedStatement.getBoundSql();
return showSql(configuration, boundSql);
/**
* 解析BoundSql,生成不含占位符的SQL语句
*
* @param configuration
* @param boundSql
* @return
*/
private 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();
String[] s = metaObject.getObjectWrapper().getGetterNames();
s.toString();
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;
/**
* 若为字符串或者日期类型,则在参数两边添加''
*
* @param obj
* @return
*/
private static String getParameterValue(Object obj)
String value = null;
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;
/**
* 获取利用反射获取类里面的值和名称
*
* @param obj
* @return
* @throws IllegalAccessException
*/
private static Map<String, Object> objectToMap(Object obj) throws IllegalAccessException
Map<String, Object> map = new HashMap<>(16);
Class<?> clazz = obj.getClass();
log.info("Class<?>=",clazz);
// 获取本类及其父类的属性,↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
List<Field> fieldList = new ArrayList<>();
while (clazz != null)
fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
clazz = clazz.getSuperclass();
// 获取本类及其父类的属性,↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
for (Field field : fieldList)
field.setAccessible(true);
String fieldName = field.getName();
Object value = field.get(obj);
map.put(fieldName, value);
return map;
/**
* 获取 DML 的 方式(insert、delete、update、select)
* @param sql
* @return
*/
public static String getSqlDML(String sql)
if(Func.isEmpty(sql))
return null;
try
sql=sql.trim();
int endIndex = sql.indexOf(" ")!=-1?sql.indexOf(" "):sql.length()>=6?6:sql.length();
return sql.substring(0, endIndex).toUpperCase();
catch (Exception e)
log.error("SqlUtils- 获取sql的DML 异常",e);
return null;
/**
* 获取表名
* @param sql
* @return
*/
public static String getTableName(String sql)
String dml = getSqlDML(sql);
if(Func.isEmpty(dml))
return null;
// LogSqlEnum.DmlEnum.UPDATE.equals(dml) || LogSqlEnum.DmlEnum.DELETE.equals(dml) || LogSqlEnum.DmlEnum.SELECT.equals(dml)
try
sql = sql.substring(6).trim().toUpperCase();
if((LogSqlConstant.DmlEnum.DELETE.equals(dml) || LogSqlConstant.DmlEnum.SELECT.equals(dml))
&& sql.contains("FROM"))
sql=sql.substring(sql.indexOf("FROM")+4).trim();
String[] s = sql.split(" ");
if(s.length>1)
return s[0].trim();
if(LogSqlConstant.DmlEnum.UPDATE.equals(dml) && sql.contains("SET"))
return sql.substring(6,sql.indexOf("SET")).trim();
return null;
catch (Exception e)
log.error("SqlUtils- 获取表名 异常",e);
return null;
/**
* 分析sql的where条件
* @param sql
* @return
*/
public static String analyzeSqlWhere(String sql)
try
String dml = getSqlDML(sql);
// 忽略枚举表
if(LogSqlConstant.DmlEnum.SELECT.equals(dml)
&& LogSqlConstant.getIgnoreTable().contains(getTableName(sql)))
return null;
if(LogSqlConstant.DmlEnum.UPDATE.equals(dml) || LogSqlConstant.DmlEnum.DELETE.equals(dml) || LogSqlConstant.DmlEnum.SELECT.equals(dml))
String sqlUpperCase = sql.toUpperCase();
if(!sqlUpperCase.contains("WHERE"))
return "【高风险sql】"+dml+"无WHERE条件";
else
String whereStr = sqlUpperCase.substring(sqlUpperCase.indexOf("WHERE")+5);
if(whereStr.contains("GROUP BY"))
whereStr = whereStr.substring(0,whereStr.indexOf("GROUP BY"));
else if(whereStr.contains("ORDER BY"))
whereStr = whereStr.substring(0,whereStr.indexOf("ORDER BY"));
whereStr = whereStr.trim();
List<String> blList = Lists.newArrayList();
if(whereStr.contains("("))
String[] bl = whereStr.split("\\\\(");
for (String b : bl)
if(Func.isNotEmpty(b))
blList.add(b);
List<String> brList = Lists.newArrayList();
for (String bl : blList)
String[] br = bl.split("\\\\)");
for (String b : br)
if(Func.isNotEmpty(b))
brList.add(b);
List<String> andList = Lists.newArrayList();
for (String br : brList)
String[] ands = br.split("AND");
for (String s : ands)
if(Func.isNotEmpty(s))
andList.add(s);
List<String> orList = Lists.newArrayList();
for (String an : andList)
String[] ors = an.split("OR");
for (String s : ors)
if(Func.isNotEmpty(s))
orList.add(s);
StringBuilder nullSB = new StringBuilder();
for (String or : orList)
String[] eqs = or.split("=");
if(eqs.length==2
&& (Func.isEmpty(eqs[1]) || "null".equalsIgnoreCase(eqs[1].trim()) ))
nullSB.append(or).append(";");
return nullSB.length()>0?nullSB.insert(0,"【中风险sql】条件值为空:").toString():null;
return null;
catch (Exception e)
log.error("SqlUtils-分析sql的where条件异常",e);
return "SqlUtils-分析sql的where条件异常";
public static void main(String[] args)
// System.out.println(getSqlDML("insertaass"));
String sql0="select * from t_test where (sex='男' or sex='女' or id in (1) ) and name=null and id=2 and age in (1,2,3) group by dept order by id desc";
String sql2="select day as day, code as code from t_day where day >= '2021-09-08' and day <= '2021-09-08' and (code = '0' or code = '2')";
// String analyzeSqlWhere = analyzeSqlWhere(sql);
// System.out.println(analyzeSqlWhere);
LogSqlConstant.java
package com.xxx.xxx.xxx.model.log;
import com.google.common.collect.Sets;
import java.util.Set;
public class LogSqlConstant
public enum DmlEnum
SELECT,INSERT,UPDATE,DELETE;
public static Set<String> getIgnoreTable()
return Sets.newHashSet("T_PRIORITY","T_STATUS_TYPE","T_B_TYPE","T_A_TYPE");
Func.java
package com.xxx.xxx.xxx.util;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* 高频方法集合类
*/
public class Func
/**
* 两个字符串数值 乘法
*
* @return
*/
public static String multiply(String arg1, String arg2)
BigDecimal bg1 = new BigDecimal(arg1);
BigDecimal bg2 = new BigDecimal(arg2);
return bg1.multiply(bg2).toString();
/**
* 比较两个对象是否相等。<br>
* 相同的条件有两个,满足其一即可:<br>
* 1. obj1 == null && obj2 == null; 2. obj1.equals(obj2)
*
* @param obj1 对象1
* @param obj2 对象2
* @return 是否相等
*/
public static boolean equals(Object obj1, Object obj2)
return (obj1 != null) ? (obj1.equals(obj2)) : (obj2 == null);
/**
* 计算对象长度,如果是字符串调用其length函数,集合类调用其size函数,数组调用其length属性,其他可遍历对象遍历计算长度
*
* @param obj 被计算长度的对象
* @return 长度
*/
// public static int length(Object obj)
// return ObjectKit.length(obj);
//
/**
* 对象中是否包含元素
*
* @param obj 对象
* @return 是否包含
*/
// public static boolean contains(Object obj, Object element)
// return ObjectKit.contains(obj, element);
//
public static boolean isNotEmpty(Object obj)
return !isEmpty(obj);
/**
* 对象是否为空
*
* @param obj String,List,Map,Object[],int[],long[]
* @return
*/
@SuppressWarnings("rawtypes")
public static boolean isEmpty(Object obj)
if (obj == null)
return true;
if (obj instanceof String)
if (obj.toString().trim().equals(""))
return true;
else if (obj instanceof List)
if (((List) obj).size() == 0)
return true;
else if (obj instanceof Map)
if (((Map) obj).size() == 0)
return true;
else if (obj instanceof Set)
if (((Set) obj).size() == 0)
return true;
else if (obj instanceof Object[])
if (((Object[]) obj).length == 0)
return true;
else if (obj instanceof int[])
if (((int[]) obj).length == 0)
return true;
else if (obj instanceof long[])
if (((long[]) obj).length == 0)
return true;
return false;
/**
* 对象组中是否存在 Empty Object
*
* @param os 对象组
* @return
*/
public static boolean isOneEmpty(Object... os)
for (Object o : os)
if (isEmpty(o))
return true;
return false;
/**
* 对象组中是否全是 Empty Object
*
* @param os
* @return
*/
public static boolean isAllEmpty(Object... os)
for (Object o : os)
if (!isEmpty(o))
return false;
return true;
/**
* 是否为数字
*
* @param obj
* @return
*/
public static boolean isNum(Object obj)
try
Integer.parseInt(obj.toString());
catch (Exception e)
return false;
return true;
/**
* 数字是否为Null或Zero
*
* @param obj
* @return
*/
public static boolean isNullOrZero(Object obj)
if (obj == null)
return true;
if (obj instanceof Integer)
try
return Integer.parseInt(obj.toString()) == 0;
catch (Exception e)
return false;
if (obj instanceof Long)
try
return Long.parseLong(obj.toString()) == 0L;
catch (Exception e)
return false;
return false;
/**
* 如果为空, 则调用默认值
*
* @param str
* @return
*/
public static Object getValue(Object str, Object defaultValue)
if (isEmpty(str))
return defaultValue;
return str;
/**
* map的key转为小写
*
* @param map
* @return Map<String , Object>
*/
public static Map<String, Object> caseInsensitiveMap(Map<String, Object> map)
Map<String, Object> tempMap = new HashMap<>();
for (String key : map.keySet())
tempMap.put(key.toLowerCase(), map.get(key));
return tempMap;
/**
* 获取map中第一个数据值
*
* @param <K> Key的类型
* @param <V> Value的类型
* @param map 数据源
* @return 返回的值
*/
public static <K, V> V getFirstOrNull(Map<K, V> map)
V obj = null;
for (Entry<K, V> entry : map.entrySet())
obj = entry.getValue();
if (obj != null)
break;
return obj;
/**
* 创建StringBuilder对象
*
* @return StringBuilder对象
*/
public static StringBuilder builder(String... strs)
final StringBuilder sb = new StringBuilder();
for (String str : strs)
sb.append(str);
return sb;
/**
* 创建StringBuilder对象
*
* @return StringBuilder对象
*/
public static void builder(StringBuilder sb, String... strs)
for (String str : strs)
sb.append(str);
/**
* 清除两边的空格
* @param sql " aa bb cc "
* @return "aa bb cc"
*/
public static String clearSideSpaces(String sql)
if(Func.isEmpty(sql))
return "";
return sql.trim();
/**
* 清除中间的空格
* @param sql " aa bb
cc "
* @return " aa bb cc "
*/
public static String clearMiddleSpaces(String sql)
if(Func.isEmpty(sql))
return "";
return sql.replaceAll("[\\\\s]+", " ");
/**
* 清除(两边+中间)的空格
* @param sql " aa bb
cc "
* @return "aa bb cc"
*/
public static String clearExtraSpaces(String sql)
if(Func.isEmpty(sql))
return "";
return sql.replaceAll("[\\\\s]+", " ").trim();
public static void main(String[] args)
String sql = " aa bb cc ";
System.out.println("--"+clearMiddleSpaces(sql)+"--");
以上是关于通过spring的AOP的切面,拦截到持久层执行的sql及参数的主要内容,如果未能解决你的问题,请参考以下文章
通过spring的AOP的切面,拦截到持久层执行的sql及参数
通过spring的AOP的切面,拦截到持久层执行的sql及参数