基於gradle自定義一個springboot starter攔截並輸出mybatis完整sql(帶完整查詢參數)

注:下面的各種命名可以隨意來,不過建議是按照博客的來,您熟悉流程之後再根據您自己的需要更改即可

創建工程

  1. 創建一個空的gradle工程
  2. 工程groupId: org.mybatislog
  3. 工程artifactId:mybatislog-spring-boot-starter
  4. 然後點擊完成即可
  5. 然後修改根目錄下的build.gradle內容如下:
plugins {
    id 'java'
    id 'maven'
}

group 'org.mybatislog'
version '1.0.0'

springBootVersion = '2.3.0.RELEASE'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenLocal()
    maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
    jcenter()
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter:2.3.0.RELEASE'
    implementation 'org.springframework.boot:spring-boot-autoconfigure:2.3.0.RELEASE'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor:2.3.0.RELEASE'
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.2'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}

// 指定上傳的路徑(本地maven倉庫)
def localMavenRepo = 'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath

// 上傳Task,Gradle會生成並上傳pom.xml文件。
uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: localMavenRepo)

            //構造項目的Pom文件
            pom.project {
                name = project.name
                packaging = 'jar'
                description = 'mybatis sql log print starter project'
            }
        }

    }
}

settting.gradle內容如下:

rootProject.name = 'mybatislog-spring-boot-starter'

開始編寫實現邏輯

  1. 在resource文件夾下創建文件夾叫META-INFO,然後在META-INFO文件夾下創建spring.factories文件,spring.factories文件內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatislog.spring.boot.starter.StarterAutoConfigure

即指定starter的配置類。

  1. 在java文件夾下創建包org.mybatislog.spring.boot.starter
  2. 在包下創建類:StarterProperties
package org.mybatislog.spring.boot.starter;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "org.mybatislog")
public class StarterProperties {
    /**
     * 是否開啓該功能
     */
    private Boolean enable;

    public Boolean getEnable() {
        return enable;
    }
    public void setEnable(Boolean enable) {
        this.enable = enable;
    }
}

其中註解ConfigurationProperties中的prefix表示在springboot中的application.properties或application.yml中的配置屬性,比如配置mybatis時

mybatis:
  mapper-locations: classpath:mapper/*.xml
  1. 創建mybatis攔截器MybatisInterceptor
package org.mybatislog.spring.boot.starter;

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.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;

@Intercepts({
        @Signature(type = StatementHandler.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = StatementHandler.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MybatisInterceptor implements Interceptor {

    private static final Logger log = LoggerFactory.getLogger(MybatisInterceptor.class.getName());

    private StarterProperties starterProperties;

    public MybatisInterceptor(StarterProperties starterProperties) {
        if (starterProperties == null) {
            this.starterProperties = new StarterProperties();
            this.starterProperties.setEnable(true);
        }
        if (starterProperties != null && starterProperties.getEnable() == null) {
            starterProperties.setEnable(true);
        }
        this.starterProperties = starterProperties;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        this.beginPluginAction(invocation);
        // 執行之前處理
        Object result = invocation.proceed();
        // 執行之後處理
        return result;
    }

    @Override
    public Object plugin(Object target) {// 複寫本方法時,最好參考官方文檔,網上博客異常的亂,運行不出來或者運行會導致mybatis本身運行異常的太多,謹慎複寫該方法
        return Proxy.newProxyInstance(Interceptor.class.getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return intercept(new Invocation(target, method, args));
            }
        });
    }

    /**
     * 主要是拿來區別數據庫,比如mysql,mssql,oracle等,這點在分頁插件上有很大作用
     * 但是目前本攔截器只是爲了將參數塞進sql中,在日誌中打印出完整sql,如果需要setProperties生效,只有在
     * mybatis的配置文件中的plugins中的plugin節點加入本攔截方法,不然本方法將無效
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        log.info("start init mybatis properties......");
    }

    /**
     * 開始執行
     *
     * @param invocation
     * @throws Throwable
     */
    private void beginPluginAction(Invocation invocation) {
        if (!starterProperties.getEnable()) {
            return;
        }
        if (invocation.getTarget() instanceof StatementHandler) {
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
            //先攔截到RoutingStatementHandler,裏面有個StatementHandler類型的delegate變量,其實現類是BaseStatementHandler,然後就到BaseStatementHandler的成員變量mappedStatement
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
            if (invocation.getArgs().length > 1) {
                Configuration configuration = mappedStatement.getConfiguration();
                // System.err.println(this.formatterSql(configuration, statementHandler.getBoundSql()));
                String type = mappedStatement.getSqlCommandType().toString();
                if (type.equals("SELECT")) {
                    log.debug("(execute query)==> " + this.formatterSql(configuration, statementHandler.getBoundSql()));
                } else if (type.equals("INSERT") || type.equals("UPDATE")) {
                    log.debug("(execute insert or update)==> " + this.formatterSql(configuration, statementHandler.getBoundSql()));
                } else {
                    log.debug("(execute delete)==> " + this.formatterSql(configuration, statementHandler.getBoundSql()));
                }
            }
        }
    }

    /**
     * 獲取查詢參數
     *
     * @param obj
     * @return
     */
    private String getParameterValue(Object obj) {
        String value;
        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(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }
        }
        return value;
    }

    /**
     * 格式化sql腳本
     *
     * @param configuration
     * @param boundSql
     * @return
     */
    public String formatterSql(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();
                    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;
    }
}


關於mybatis攔截器知識可以搜其他博客來看看,或者直接讀mybatis源碼,有空的時候也可以瞭解一下mybatis的三大接口及三大接口的執行順序及生命週期,這對你自定義mybatis時有很大的作用。

  1. 編寫最後的配置類
package org.mybatislog.spring.boot.starter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 
 * @author jy
 * @類說明:自動裝配
 */
@Configuration
@EnableConfigurationProperties(StarterProperties.class)
public class StarterAutoConfigure {

    private static final Logger LOGGER = LoggerFactory.getLogger(StarterAutoConfigure.class);

    @Autowired
    private StarterProperties properties;

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "org.mybatislog", value = "enable", havingValue = "true")
    public MybatisInterceptor initMybatisInterceptor() {
        LOGGER.info("開始執行sql打印操作..................................");
        return new MybatisInterceptor(properties);
    }
}

  1. 上傳到本地maven倉庫中
    在這裏插入圖片描述

  2. 如果提示成功,然後再隨意創建一個新的gradle工程,直接在新工程的build.gradle中加上
    的repositories中必須依賴使用本地maven倉庫,內容如下:

repositories {
	mavenLocal()
	maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
	mavenCentral()
	jcenter()
}

切記在新工程必須配置mybatis的相關工作,讓工程能正常跑,否則接下來的事就都扯淡了。

然後在依賴中引用剛剛發佈的庫

implementation 'org.mybatislog:mybatislog-spring-boot-starter:1.0.0'
  1. 在新工程application.yml中添加如下內容:
logging:
  level:
    org:
      mybatislog: debug
org:
  mybatislog:
    enable: true
  1. 運行新工程後,截圖如下:
    在這裏插入圖片描述

好了,至此我們創建攔截並打印完整sql的一個springboot的starter就完成了。

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