注:下面的各種命名可以隨意來,不過建議是按照博客的來,您熟悉流程之後再根據您自己的需要更改即可
創建工程
- 創建一個空的gradle工程
- 工程groupId: org.mybatislog
- 工程artifactId:mybatislog-spring-boot-starter
- 然後點擊完成即可
- 然後修改根目錄下的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'
開始編寫實現邏輯
- 在resource文件夾下創建文件夾叫
META-INFO
,然後在META-INFO
文件夾下創建spring.factories文件,spring.factories文件內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatislog.spring.boot.starter.StarterAutoConfigure
即指定starter的配置類。
- 在java文件夾下創建包
org.mybatislog.spring.boot.starter
- 在包下創建類: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
- 創建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時有很大的作用。
- 編寫最後的配置類
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);
}
}
-
上傳到本地maven倉庫中
-
如果提示成功,然後再隨意創建一個新的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'
- 在新工程application.yml中添加如下內容:
logging:
level:
org:
mybatislog: debug
org:
mybatislog:
enable: true
- 運行新工程後,截圖如下:
好了,至此我們創建攔截並打印完整sql的一個springboot的starter就完成了。