Mybatis攔截器打印sql問題
在項(xiàng)目中,通常會配置log4j2等來輸出mybatis的sql,為了防止sql注入問題,我們通常會使用#{}的方式來注入sql的參數(shù),這會導(dǎo)致我們拿到的sql日志是沒替換參數(shù)的,參數(shù)都是通過問號?占位符的方式。
當(dāng)我們需要拿下sql去數(shù)據(jù)庫客戶端執(zhí)行的時(shí)候,就會有一個困擾:需要把一個個問號?
替換成對應(yīng)的參數(shù),假設(shè)在insert的sql中插入一個有幾十個字段的表,那將會是一場噩夢。
那有沒有辦法在日志文件記錄完整的sql呢?
詳細(xì)請看下面步驟。
1.log4j2配置修改
關(guān)閉log4j2打印mybatis的sql配置,如果本來就沒開啟,那不需要。
2.配置日志開關(guān)
在springboot的yml文件配置變量logging.sql.enable=true,對攔截器做開關(guān)控制。
3.添加攔截器插件
// 采用自定義攔截器打印sql日志 sessionFactory.setPlugins(new Interceptor[]{this.getSqlLogInterceptor()});
4.攔截器邏輯描述
4.1 注入開關(guān)
?@Value("${logging.sql.enable:true}") ?private Boolean sqlEnable;
4.2 獲取sql
Object target = invocation.getTarget(); StatementHandler statementHandler = (StatementHandler) target; ?//獲取綁定的SQL對象 BoundSql boundSql = statementHandler.getBoundSql(); ?//得到需要執(zhí)行的sql語句,并進(jìn)行格式 String sql = boundSql.getSql();
4.2 獲取參數(shù)
//需要綁定的參數(shù)映射對象 List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
4.3 sql替換參數(shù)
public String buidSql(String sql,Object[] parameters) { ? ? ? ? if (parameters == null || sql == null) { ? ? ? ? ? ? return ""; ? ? ? ? } ? ? ? ? List<Object> parametersArray = Arrays.asList(parameters); ? ? ? ? List<Object> list = new ArrayList<Object>(parametersArray); ? ? ? ? while (sql.indexOf("?") != -1 && list.size() > 0 && parameters.length > 0) { ? ? ? ? ? ? Object obj = list.get(0); ? ? ? ? ? ? if(null!=obj && obj instanceof String){ ? ? ? ? ? ? ? ? sql = sql.replaceFirst("\\?", "'"+obj.toString()+"'"); ? ? ? ? ? ? }else if(null!=obj){ ? ? ? ? ? ? ? ? sql = sql.replaceFirst("\\?", obj.toString()); ? ? ? ? ? ? } ? ? ? ? ? ? ? list.remove(0); ? ? ? ? } ? ? ? ? return sql.replaceAll("(\r?\n(\\s*\r?\n)+)", "\r\n"); ? ? }
4.4 打印sql
log.debug(String.format( ? ? ? ? ? ? ? ? ? ? ? ? "\n########################### ? Sql Start ?###########################" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n StartTime ?: ?%s" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n ExecuteID ?: ?%s" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n ExecuteSQL ?: ?%s" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n ExecuteTime ?: ?%s ms" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n########################### ? Sql End ?###########################\n" ? ? ? ? ? ? ? ? ? ? ? ? ? ,startTimeStr,executeID,this.buidSql(sql,args.toString().split(",")),exeTime));
4.5打印效果
攔截器完整代碼
package com.cloudpaas.plugin.mybatis.interceptor; ? import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ParameterMode; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.type.TypeHandlerRegistry; import org.springframework.beans.factory.annotation.Value; ? import java.lang.reflect.Field; import java.sql.Statement; import java.text.SimpleDateFormat; import java.util.*; ? @Intercepts({ ? ? ? ? @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), ? ? ? ? @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), ? ? ? ? @Signature(type = StatementHandler.class, method = "batch", args = { Statement.class }) }) @Slf4j public class SqlLogInterceptor implements Interceptor { ? ? private SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ? ? ? @Value("${logging.sql.enable:true}") ? ? private Boolean sqlEnable; ? ? ? @Override ? ? public Object intercept(Invocation invocation) throws Throwable { ? ? ? ? if(!sqlEnable){ ? ? ? ? ? ? return invocation.proceed(); ? ? ? ? } ? ? ? ? Object target = invocation.getTarget(); ? ? ? ? MetaObject mObject = SystemMetaObject.forObject(invocation.getTarget()); ? ? ? ? MappedStatement mappedStatement = (MappedStatement)mObject.getValue("delegate.mappedStatement"); ? ? ? ? // 執(zhí)行的mapper statement ID ? ? ? ? String executeID = mappedStatement.getId(); ? ? ? ? //獲取當(dāng)前的開始時(shí)間戳 ? ? ? ? long startTime = System.currentTimeMillis(); ? ? ? ? //記錄當(dāng)前時(shí)間 ? ? ? ? String startTimeStr=sdf.format(new Date()); ? ? ? ? StatementHandler statementHandler = (StatementHandler) target; ? ? ? ? try { ? ? ? ? ? ? return invocation.proceed(); ? ? ? ? } finally { ? ? ? ? ? ? long endTime = System.currentTimeMillis(); ? ? ? ? ? ? //sql的執(zhí)行的時(shí)間 ? ? ? ? ? ? long exeTime = endTime - startTime; ? ? ? ? ? ? try{ ? ? ? ? ? ? ? ? //獲取綁定的SQL對象 ? ? ? ? ? ? ? ? BoundSql boundSql = statementHandler.getBoundSql(); ? ? ? ? ? ? ? ? //得到需要執(zhí)行的sql語句,并進(jìn)行格式 ? ? ? ? ? ? ? ? String sql = boundSql.getSql(); ? ? ? ? ? ? ? ? sql=formatSql(sql); ? ? ? ? ? ? ? ? //得到默認(rèn)的參數(shù)處理器 ? ? ? ? ? ? ? ? DefaultParameterHandler dph=(DefaultParameterHandler)statementHandler.getParameterHandler(); ? ? ? ? ? ? ? ? //利用反射機(jī)制,從DefaultParameterHandler獲取Configuration和TypeHandlerRegistry ? ? ? ? ? ? ? ? Field configurationField=dph.getClass().getDeclaredField("configuration"); ? ? ? ? ? ? ? ? Field typeHandlerRegistryField=dph.getClass().getDeclaredField("typeHandlerRegistry"); ? ? ? ? ? ? ? ? //設(shè)置私有屬性可訪問 ? ? ? ? ? ? ? ? configurationField.setAccessible(true); ? ? ? ? ? ? ? ? //設(shè)置私有屬性可訪問 ? ? ? ? ? ? ? ? typeHandlerRegistryField.setAccessible(true); ? ? ? ? ? ? ? ? Configuration configuration=(Configuration) configurationField.get(dph); ? ? ? ? ? ? ? ? TypeHandlerRegistry typeHandlerRegistry=(TypeHandlerRegistry) typeHandlerRegistryField.get(dph); ? ? ? ? ? ? ? ? //sql的參數(shù)對象 ? ? ? ? ? ? ? ? Object parameterObject = boundSql.getParameterObject(); ? ? ? ? ? ? ? ? //需要綁定的參數(shù)映射對象 ? ? ? ? ? ? ? ? List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings(); ? ? ? ? ? ? ? ? //處理sql的參數(shù),該部分參考的是DefaultParameterHandler中setParameters方法中的實(shí)現(xiàn) ? ? ? ? ? ? ? ? StringBuffer args=new StringBuffer(); ? ? ? ? ? ? ? ? if(parameterMappingList!=null && parameterMappingList.size()>0){ ? ? ? ? ? ? ? ? ? ? for(ParameterMapping parameterMapping:parameterMappingList){ ? ? ? ? ? ? ? ? ? ? ? ? //如果該參數(shù)不是輸出參數(shù),則進(jìn)行處理 ? ? ? ? ? ? ? ? ? ? ? ? if (parameterMapping.getMode() != ParameterMode.OUT) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? Object value; ? ? ? ? ? ? ? ? ? ? ? ? ? ? //參數(shù)的名字,屬性 ? ? ? ? ? ? ? ? ? ? ? ? ? ? String propertyName = parameterMapping.getProperty(); ? ? ? ? ? ? ? ? ? ? ? ? ? ? //先從附加的,主要是list、array等的處理 ? ? ? ? ? ? ? ? ? ? ? ? ? ? if (boundSql.hasAdditionalParameter(propertyName)) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? value = boundSql.getAdditionalParameter(propertyName); ? ? ? ? ? ? ? ? ? ? ? ? ? ? } else if (parameterObject == null) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? value = null; ? ? ? ? ? ? ? ? ? ? ? ? ? ? } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //typeHandlerRegistry注冊了某個類的處理 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? value = parameterObject; ? ? ? ? ? ? ? ? ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //默認(rèn)的MetaObject 的處理,根據(jù)參數(shù)獲取值 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? MetaObject metaObject = configuration.newMetaObject(parameterObject); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? value = metaObject.getValue(propertyName); ? ? ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ? if(value!=null){ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? if(value instanceof Date){ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //如果是日期,則格式化一下 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? value=sdf.format(value); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ? args.append(",").append(value); ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? //刪除第一個逗號 ? ? ? ? ? ? ? ? ? ? args.deleteCharAt(0); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? log.debug(String.format( ? ? ? ? ? ? ? ? ? ? ? ? "\n########################### ? Sql Start ?###########################" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n StartTime ?: ?%s" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n ExecuteID ?: ?%s" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n ExecuteSQL ?: ?%s" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n ExecuteTime ?: ?%s ms" + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? "\n########################### ? Sql End ?###########################\n" ? ? ? ? ? ? ? ? ? ? ? ? ? ,startTimeStr,executeID,this.buidSql(sql,args.toString().split(",")),exeTime)); ? ? ? ? ? ? }catch(Exception e){ ? ? ? ? ? ? } ? ? ? ? } ? ? } ? ? ? @Override ? ? public Object plugin(Object target) { ? ? ? ? return Plugin.wrap(target, this); ? ? } ? ? ? @Override ? ? public void setProperties(Properties properties) { ? ? ? } ? ? ? private String formatSql(String sql) { ? ? ? ? // 輸入sql字符串空判斷 ? ? ? ? if (sql == null || sql.length() == 0) { ? ? ? ? ? ? return ""; ? ? ? ? } ? ? ? ? //格式sql 將回車換行制表符等替換成空,在將連續(xù)多個空格替換成1個空格,然后在去掉左右括號兩邊的空格,在去掉逗號左右兩個的空格 ? ? ? ? return sql.replaceAll("[\\t\\n\\x0B\\f\\r]", "").replaceAll(" +", " ") ? ? ? ? ? ? ? ? .replaceAll(" *\\( *", "(").replaceAll(" *\\) *", ")").replaceAll(" *, *", ","); ? ? ? } ? ? ? public String buidSql(String sql,Object[] parameters) { ? ? ? ? if (parameters == null || sql == null) { ? ? ? ? ? ? return ""; ? ? ? ? } ? ? ? ? List<Object> parametersArray = Arrays.asList(parameters); ? ? ? ? List<Object> list = new ArrayList<Object>(parametersArray); ? ? ? ? while (sql.indexOf("?") != -1 && list.size() > 0 && parameters.length > 0) { ? ? ? ? ? ? Object obj = list.get(0); ? ? ? ? ? ? if(null!=obj && obj instanceof String){ ? ? ? ? ? ? ? ? sql = sql.replaceFirst("\\?", "'"+obj.toString()+"'"); ? ? ? ? ? ? }else if(null!=obj){ ? ? ? ? ? ? ? ? sql = sql.replaceFirst("\\?", obj.toString()); ? ? ? ? ? ? } ? ? ? ? ? ? ? list.remove(0); ? ? ? ? } ? ? ? ? return sql.replaceAll("(\r?\n(\\s*\r?\n)+)", "\r\n"); ? ? } }
總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
- MybatisPlusInterceptor實(shí)現(xiàn)sql攔截器超詳細(xì)教程
- Mybatis之如何攔截慢SQL日志記錄
- Mybatis-Plus實(shí)現(xiàn)SQL攔截器的示例
- 利用Mybatis?Plus實(shí)現(xiàn)一個SQL攔截器
- MyBatis如何通過攔截修改SQL
- spring?boot?Mybatis?攔截器實(shí)現(xiàn)拼接sql和修改的代碼詳解
- MyBatis自定義SQL攔截器示例詳解
- mybatis 通過攔截器打印完整的sql語句以及執(zhí)行結(jié)果操作
- mybatis 實(shí)現(xiàn) SQL 查詢攔截修改詳解
- MyBatis的動態(tài)攔截sql并修改
相關(guān)文章
java面試LruCache?和?LinkedHashMap及算法實(shí)現(xiàn)
這篇文章主要為大家介紹了java面試LruCache?和?LinkedHashMap及算法實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Mybatis之@ResultMap,@Results,@Result注解的使用
這篇文章主要介紹了Mybatis之@ResultMap,@Results,@Result注解的使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12Springboot接口項(xiàng)目如何使用AOP記錄日志
這篇文章主要介紹了Springboot接口項(xiàng)目如何使用AOP記錄日志,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Java中的Runnable,Callable,F(xiàn)uture,F(xiàn)utureTask的比較
這篇文章主要介紹了Java中的Runnable,Callable,F(xiàn)uture,F(xiàn)utureTask的比較的相關(guān)資料,需要的朋友可以參考下2017-02-02