Mybatis攔截器打印sql問題
在項目中,通常會配置log4j2等來輸出mybatis的sql,為了防止sql注入問題,我們通常會使用#{}的方式來注入sql的參數(shù),這會導致我們拿到的sql日志是沒替換參數(shù)的,參數(shù)都是通過問號?占位符的方式。
當我們需要拿下sql去數(shù)據(jù)庫客戶端執(zhí)行的時候,就會有一個困擾:需要把一個個問號?
替換成對應(yīng)的參數(shù),假設(shè)在insert的sql中插入一個有幾十個字段的表,那將會是一場噩夢。
那有沒有辦法在日志文件記錄完整的sql呢?
詳細請看下面步驟。
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語句,并進行格式 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();
? ? ? ? //獲取當前的開始時間戳
? ? ? ? long startTime = System.currentTimeMillis();
? ? ? ? //記錄當前時間
? ? ? ? String startTimeStr=sdf.format(new Date());
? ? ? ? StatementHandler statementHandler = (StatementHandler) target;
? ? ? ? try {
? ? ? ? ? ? return invocation.proceed();
? ? ? ? } finally {
? ? ? ? ? ? long endTime = System.currentTimeMillis();
? ? ? ? ? ? //sql的執(zhí)行的時間
? ? ? ? ? ? long exeTime = endTime - startTime;
? ? ? ? ? ? try{
? ? ? ? ? ? ? ? //獲取綁定的SQL對象
? ? ? ? ? ? ? ? BoundSql boundSql = statementHandler.getBoundSql();
? ? ? ? ? ? ? ? //得到需要執(zhí)行的sql語句,并進行格式
? ? ? ? ? ? ? ? String sql = boundSql.getSql();
? ? ? ? ? ? ? ? sql=formatSql(sql);
? ? ? ? ? ? ? ? //得到默認的參數(shù)處理器
? ? ? ? ? ? ? ? DefaultParameterHandler dph=(DefaultParameterHandler)statementHandler.getParameterHandler();
? ? ? ? ? ? ? ? //利用反射機制,從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方法中的實現(xiàn)
? ? ? ? ? ? ? ? StringBuffer args=new StringBuffer();
? ? ? ? ? ? ? ? if(parameterMappingList!=null && parameterMappingList.size()>0){
? ? ? ? ? ? ? ? ? ? for(ParameterMapping parameterMapping:parameterMappingList){
? ? ? ? ? ? ? ? ? ? ? ? //如果該參數(shù)不是輸出參數(shù),則進行處理
? ? ? ? ? ? ? ? ? ? ? ? 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 {
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //默認的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)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis中foreach標簽的collection屬性的取值方式
這篇文章主要介紹了MyBatis中foreach標簽的collection屬性的取值方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
解決Springboot @Autowired 無法注入問題
WebappApplication 一定要在包的最外層,否則Spring無法對所有的類進行托管,會造成@Autowired 無法注入。接下來給大家介紹解決Springboot @Autowired 無法注入問題,感興趣的朋友一起看看吧2018-08-08
Spring事務(wù)管理只對出現(xiàn)運行期異常進行回滾
Spring的事務(wù)管理默認只對出現(xiàn)運行期異常(java.lang.RuntimeException及其子類)進行回滾,需要了解更多Spring事務(wù)方面的知識,可詳看本文2012-11-11
Swagger異常定位紀實Swagger設(shè)計問題分析
這篇文章主要為大家介紹了Swagger異常定位紀實Swagger設(shè)計的問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-02-02
spring-boot整合dubbo:Spring-boot-dubbo-starter
這篇文章主要介紹了spring-boot整合dubbo:Spring-boot-dubbo-starter的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-05-05
Java中java.lang.ClassCastException異常原因及解決方法
大家好,本篇文章主要講的是Java中java.lang.ClassCastException異常原因及解決方法,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01

