mybatis獲取實際執行的sql語句

最近項目中的一個需求:涉及到數據同步,由於環境原因導致只能通過webservice同步執行的sql語句,故需要獲取數據庫實際執行的增刪改sql語句到sql_log表中以同步。

編寫mybatis插件攔截sql語句

package net.mshome.twisted.tmall.interceptor;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.enums.IEnum;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
import lombok.extern.slf4j.Slf4j;
import net.mshome.twisted.tmall.entity.SqlLog;
import net.mshome.twisted.tmall.service.ISqlLogService;
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.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.CollectionUtils;

import java.sql.Connection;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

/**
 * 自定義mybatis插件,實現輸出實際執行sql語句
 *
 * @author [email protected]
 * @date 2019-08-18
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
@Slf4j
public class MybatisSqlInterceptor extends AbstractSqlParserHandler implements Interceptor {

    /**
     * 獲取配置中需要攔截的表
     */
    @Value("#{'${tmall.sync.tables:}'.split(',')}")
    private List<String> tableNames;

    @Lazy
    @Autowired
    private ISqlLogService sqlLogService;

    /**
     * 忽略插入sql_log表的語句
     */
    private static final String IGNORE_SQL_PREFIX = "insert into sql_log";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (CollectionUtils.isEmpty(tableNames)) {
            return invocation.proceed();
        }
        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        String sql = boundSql.getSql().replaceAll("\\s+", " ").toLowerCase();
        if (sql.toLowerCase().startsWith(IGNORE_SQL_PREFIX)) {
            return invocation.proceed();
        }

        List<ParameterMapping> parameterMappings = new ArrayList<>(boundSql.getParameterMappings());
        Object parameterObject = boundSql.getParameterObject();
        if (parameterMappings.isEmpty() && parameterObject == null) {
            log.warn("parameterMappings is empty or parameterObject is null");
            return invocation.proceed();
        }

        Configuration configuration = mappedStatement.getConfiguration();
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

        try {
            this.sqlParser(metaObject);

            String parameter = "null";
            MetaObject newMetaObject = configuration.newMetaObject(parameterObject);
            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getMode() == ParameterMode.OUT) {
                    continue;
                }
                String propertyName = parameterMapping.getProperty();
                if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    parameter = getParameterValue(parameterObject);
                } else if (newMetaObject.hasGetter(propertyName)) {
                    parameter = getParameterValue(newMetaObject.getValue(propertyName));
                } else if (boundSql.hasAdditionalParameter(propertyName)) {
                    parameter = getParameterValue(boundSql.getAdditionalParameter(propertyName));
                }
                // fixme 此處不嚴謹,若sql語句中有❓,則替換錯位。🤔️
                sql = sql.replaceFirst("\\?", parameter);
            }
            // 將攔截到的sql語句插入日誌表中
            sqlLogService.save(SqlLog.builder().executedSql(sql).build());
        } catch (Exception e) {
            log.error(String.format("intercept sql error: [%s]", sql), e);
        }
        return invocation.proceed();
    }

    /**
     * 獲取參數
     *
     * @param param Object類型參數
     * @return 轉換之後的參數
     */
    private static String getParameterValue(Object param) {
        if (param == null) {
            return "null";
        }
        if (param instanceof Number) {
            return param.toString();
        }
        String value = null;
        if (param instanceof String) {
            value = param.toString();
        } else if (param instanceof Date) {
            DateUtil.format((Date) param, "yyyy-MM-dd HH:mm:ss");
        } else if (param instanceof IEnum) {
            value = String.valueOf(((IEnum) param).getValue());
        } else {
            value = param.toString();
        }
        return StringUtils.quotaMark(value);
    }


    @Override
    public Object plugin(Object o) {
        if (o instanceof StatementHandler) {
            return Plugin.wrap(o, this);
        }
        return o;
    }

    @Override
    public void setProperties(Properties properties) {

    }

}


1、mybatis插件通過攔截起方式實現,此處實現一個攔截器,攔截StatementHandler類中2個方法。
2、類似的還有mybatis-plus自帶的分頁插件,可參考Interceptor接口的實現類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章