自定義mybatis插件如何實(shí)現(xiàn)sql日志打印
mysql插件實(shí)現(xiàn)原理
官網(wǎng)的關(guān)鍵信息
- 參考文檔
https://mybatis.org/mybatis-3/zh/configuration.html#plugins
- 官方文檔
MyBatis 允許你在映射語(yǔ)句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。
默認(rèn)情況下,MyBatis 允許使用插件來(lái)攔截的方法調(diào)用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
插件運(yùn)行源碼流程
mybatis 在 配置文件里面設(shè)置 plug , 所以 configuration 可以添加插件
Configuration.java
- Executor
- ParameterHandler
- ResultSetHandler
- StatementHandler
通過查看源碼 , 知道插件增強(qiáng)的是 mybatis 以上的幾個(gè)對(duì)象
interceptorChain
interceptorChain.pluginAll(…)方法
mybatis 中的 InterceptorChain 類似于 springmvc 中的 攔截器鏈
Interceptor
實(shí)現(xiàn) InvocationHandler , 我們知道 Plugin 類使用了 JDK 動(dòng)態(tài)代理
最后執(zhí)行插件方法 , 調(diào)用的是該類的 invoke 方法
inovke方法
參考pagehelper
實(shí)現(xiàn)sql日志打印插件
前置環(huán)境
springboot + mybatis + mysql
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springboot-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.3</version> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>2.0.4</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.72</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
實(shí)現(xiàn) Interceptor 接口
package com.example.plug; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; 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 java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Properties; /** * 1.可以用來(lái)分析SQL執(zhí)行效率 * 2.可以用來(lái)獲取實(shí)際執(zhí)行的SQL */ @Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})} ) @Slf4j public class SqlInterceptor implements Interceptor { // private static final Log log = LogFactory.getLog(SqlInterceptor.class); /** * 最小打印時(shí)間 sql時(shí)間超過這個(gè)值才打印日志 毫秒 **/ private int MIN_SIZE = 0; @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; Object parameter = null; if (invocation.getArgs().length > 1) { parameter = invocation.getArgs()[1]; } String sqlId = mappedStatement.getId(); BoundSql boundSql = mappedStatement.getBoundSql(parameter); Configuration configuration = mappedStatement.getConfiguration(); long startTime = System.currentTimeMillis(); Object result = null; try { result = invocation.proceed(); } finally { try { long sqlCostTime = System.currentTimeMillis() - startTime; String sql = getSql(configuration, boundSql); formatSqlLog(mappedStatement.getSqlCommandType(), sqlId, sql, sqlCostTime, result); } catch (Exception ignored) { log.error("SQL插件執(zhí)行失敗 Mapper:{} 參數(shù)對(duì)象:{}", sqlId, JSON.toJSONString(boundSql.getParameterObject()), ignored); } } return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { if (properties == null) { return; } if (properties.containsKey("minLogSize")) { MIN_SIZE = Integer.valueOf(properties.getProperty("minLogSize")); } } private String getSql(Configuration configuration, BoundSql boundSql) { // 輸入sql字符串空判斷 String sql = boundSql.getSql(); if (StrUtil.isBlank(sql)) { return ""; } //去掉換行符 sql = sql.replaceAll("[\\s\n ]+", " "); //填充占位符, 目前基本不用mybatis存儲(chǔ)過程調(diào)用,故此處不做考慮 Object parameterObject = boundSql.getParameterObject(); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (!parameterMappings.isEmpty() && parameterObject != null) { TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { sql = this.replacePlaceholder(sql, 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 = replacePlaceholder(sql, obj); } else if (boundSql.hasAdditionalParameter(propertyName)) { Object obj = boundSql.getAdditionalParameter(propertyName); sql = replacePlaceholder(sql, obj); } } } } return sql; } private String replacePlaceholder(String sql, Object parameterObject) { String result; if (parameterObject == null) { result = "NULL"; } else if (parameterObject instanceof String) { result = String.format("'%s'", parameterObject.toString()); } else if (parameterObject instanceof Date) { result = String.format("'%s'", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(parameterObject)); } else { result = parameterObject.toString(); } return sql.replaceFirst("\\?", result); } private void formatSqlLog(SqlCommandType sqlCommandType, String sqlId, String sql, long costTime, Object obj) { if (costTime > MIN_SIZE) { if (sqlCommandType == SqlCommandType.UPDATE || sqlCommandType == SqlCommandType.INSERT || sqlCommandType == SqlCommandType.DELETE) { log.info("[{}ms] [{}] {}; 影響行數(shù):{}", costTime, sqlId, sql, obj); } if (sqlCommandType == SqlCommandType.SELECT) { log.info("[{}ms] [{}] {}; 結(jié)果行數(shù):{}", costTime, sqlId, sql, ((Collection<?>) obj).size()); } } } }
mybatis 添加該插件
SpringBoot 中 SqlSessionFactory 是單例的
通過 SqlSessionFactory
得到 Configuration
configuration 的 addInterceptor 方法添加插件
@Configuration public class MybatisPlugConfig implements SmartInitializingSingleton { @Autowired private SqlSessionFactory sqlSessionFactory; @Override public void afterSingletonsInstantiated() { sqlSessionFactory.getConfiguration().addInterceptor(new SqlInterceptor()); } }
測(cè)試
測(cè)試方便 , 省略service
controller
@RestController @RequestMapping("/test") public class HelloController { @Autowired private PersonDao personDao; @GetMapping("/ssm") public List<PersonEntity> ssm() { return personDao.list(); } @GetMapping("/insert") public String insert(){ PersonEntity personEntity = new PersonEntity(); personEntity.setPid(null); personEntity.setPname(UUID.randomUUID().toString().substring(8).toString()); personDao.insert(personEntity); return "插入成功"; } @GetMapping("/select") public List<PersonEntity> select(Integer pid , String pname){ return personDao.selectByParam(pid , pname); } }
dao
@Mapper public interface PersonDao { @Select("select * from person") List<PersonEntity> list(); int insert(PersonEntity personEntity); List<PersonEntity> selectByParam(@Param("pid") Integer pid, @Param("pname") String pname); }
控制臺(tái)打印
2022-10-28 22:07:18.913 INFO 26316 — [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet ‘dispatcherServlet’
2022-10-28 22:07:18.913 INFO 26316 — [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet ‘dispatcherServlet’
2022-10-28 22:07:18.917 INFO 26316 — [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
2022-10-28 22:07:27.247 INFO 26316 — [nio-8080-exec-6] com.example.plug.SqlInterceptor : [48ms] [com.example.dao.PersonDao.list] select * from person; 結(jié)果行數(shù):5
2022-10-28 22:07:30.759 INFO 26316 — [nio-8080-exec-8] com.example.plug.SqlInterceptor : [34ms] [com.example.dao.PersonDao.selectByParam] select * from person WHERE pname like concat(‘%’,concat(‘1’,‘%’)) and pid = 1; 結(jié)果行數(shù):0
2022-10-28 22:07:32.936 INFO 26316 — [nio-8080-exec-9] com.example.plug.SqlInterceptor : [31ms] [com.example.dao.PersonDao.selectByParam] select * from person WHERE pname like concat(‘%’,concat(‘1’,‘%’)); 結(jié)果行數(shù):2
2022-10-28 22:07:36.464 INFO 26316 — [io-8080-exec-10] com.example.plug.SqlInterceptor : [34ms] [com.example.dao.PersonDao.selectByParam] select * from person WHERE pid = 1; 結(jié)果行數(shù):1
2022-10-28 22:07:38.605 INFO 26316 — [nio-8080-exec-1] com.example.plug.SqlInterceptor : [33ms] [com.example.dao.PersonDao.selectByParam] select * from person; 結(jié)果行數(shù):5
oncat(‘1’,‘%’)); 結(jié)果行數(shù):2
2022-10-28 22:07:36.464 INFO 26316 — [io-8080-exec-10] com.example.plug.SqlInterceptor : [34ms] [com.example.dao.PersonDao.selectByParam] select * from person WHERE pid = 1; 結(jié)果行數(shù):1
2022-10-28 22:07:38.605 INFO 26316 — [nio-8080-exec-1] com.example.plug.SqlInterceptor : [33ms] [com.example.dao.PersonDao.selectByParam] select * from person; 結(jié)果行數(shù):5
2022-10-28 22:07:45.440 INFO 26316 — [nio-8080-exec-2] com.example.plug.SqlInterceptor : [53ms] [com.example.dao.PersonDao.insert] INSERT INTO person ( pid, pname ) VALUES ( 6, ‘-9bac-472f-a95c-9d97b11ab0e1’ ); 影響行數(shù):1
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)上傳圖片并壓縮圖片大小功能
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)上傳圖片并壓縮圖片大小功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05idea2023創(chuàng)建JavaWeb教程之右鍵沒有Servlet的問題解決
最近在寫一個(gè)javaweb項(xiàng)目,但是在IDEA中創(chuàng)建好項(xiàng)目后,在搭建結(jié)構(gòu)的時(shí)候創(chuàng)建servlet文件去沒有選項(xiàng),所以這里給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于idea2023創(chuàng)建JavaWeb教程之右鍵沒有Servlet問題的解決方法,需要的朋友可以參考下2023-10-10教你在Spring Boot微服務(wù)中集成gRPC通訊的方法
這篇文章主要介紹了教你在Spring Boot微服務(wù)中集成gRPC通訊的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09當(dāng)面試官問我ArrayList和LinkedList哪個(gè)更占空間時(shí),我是這么答的(面試官必問)
今天介紹一下Java的兩個(gè)集合類,ArrayList和LinkedList,這兩個(gè)集合的知識(shí)點(diǎn)幾乎可以說(shuō)面試必問的。感興趣的朋友跟隨小編一起看看吧2020-08-08Java通過word模板實(shí)現(xiàn)創(chuàng)建word文檔報(bào)告
這篇文章主要為大家詳細(xì)介紹了Java如何通過word模板實(shí)現(xiàn)創(chuàng)建word文檔報(bào)告的教程,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以學(xué)習(xí)一下2022-09-09Javaweb開發(fā)環(huán)境Myeclipse6.5 JDK1.6 Tomcat6.0 SVN1.8配置教程
這篇文章主要介紹了Javaweb開發(fā)環(huán)境Myeclipse6.5 JDK1.6 Tomcat6.0 SVN1.8配置教程,感興趣的小伙伴們可以參考一下2016-06-06SpringBoot整合EasyExcel實(shí)現(xiàn)導(dǎo)入導(dǎo)出數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了如何使用Vue、SpringBoot和EasyExcel實(shí)現(xiàn)導(dǎo)入導(dǎo)出數(shù)據(jù)功能,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-05-05