P6Spy是一個輕量級框架,只需簡單配置,就可以無縫地?cái)r截和記錄數(shù)據(jù)庫執(zhí)行sql以及耗時,而無需對現(xiàn)有應(yīng)用程序進(jìn)行代碼更改。其原理是包裝原有的數(shù)據(jù)源,在SQL執(zhí)行前后做一些功能增強(qiáng)。

簡介
P6Spy是一個輕量級框架,只需簡單配置,就可以無縫地?cái)r截和記錄數(shù)據(jù)庫執(zhí)行sql以及耗時,而無需對現(xiàn)有應(yīng)用程序進(jìn)行代碼更改。其原理是包裝原有的數(shù)據(jù)源,在sql執(zhí)行前后做一些功能增強(qiáng)。

執(zhí)行效果如圖
集成方式
P6Spy提供了3種集成方式:
- Datasource way:如果我們的項(xiàng)目中使用了自定義的Datasource,可以使用P6DataSource對原有的數(shù)據(jù)源進(jìn)行包裝,只需將自定義的DataSource傳入P6DataSource的構(gòu)造函數(shù)中即可,這也是使用P6Spy最簡單的一種方式。
- Connection URL way:由于我們一般都是用框架提供的數(shù)據(jù)源,所以P6Spy也提供了對鏈接進(jìn)行包裝的功能。只要在配置數(shù)據(jù)源時,稍微修改配置的屬性,即可使用P6Spy的功能。
- Spring Boot autoconfiguration:隨著Spring boot的流行,P6Spy還提供了基于Springboot的自動配置。
如何使用
本節(jié)主要針對第2種方式的使用進(jìn)行講解。
步驟一,引入P6Spy。
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
步驟二,修改數(shù)據(jù)源的配置。
spring:
datasource:
//此處的URL增加了p6spy
url: jdbc:p6spy:mysql://127.0.0.1:3306/dbName
username: username
password: pwd123456
//將普通的MySQL驅(qū)動替換為P6SpyDriver
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
步驟三,新建spy.properties文件,放在resources目錄下。
module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
//注意此行配置
logMessageFormat=com.shishan.demo2023.bean.P6SpyMessageFormattingStrategy
appender=com.p6spy.engine.spy.appender.Slf4JLogger
deregisterdrivers=true
useprefix=true
excludecategories=info,debug,result,resultset
dateformat=yyyy-MM-dd HH:mm:ss
outagedetection=true
outagedetectioninterval=2
filter=true
exclude=QRTZ_JOB_DETAILS,QRTZ_TRIGGERS,QRTZ_CRON_TRIGGERS,QRTZ_LOCKS,QRTZ_FIRED_TRIGGERS,QRTZ_PAUSED_TRIGGER_GRPS,QRTZ_SCHEDULER_STATE
需要注意配置參數(shù)logMessageFormat,這里需要指定一個類,這個類實(shí)現(xiàn)MessageFormattingStrategy即可自由打印我們的sql。
步驟四,新建一個類實(shí)現(xiàn)MessageFormattingStrategy,并重寫formatMessage方法。
@Slf4j
public class P6SpyMessageFormattingStrategy implements MessageFormattingStrategy {
@Override
public String formatMessage(int connectionId, String now, long elapsed,
String category, String prepared, String sql, String url) {
//執(zhí)行時間大于1000ms時,打印慢sql
if (elapsed > 1000) {
log.warn("slow sql,耗時:{}ms,sql:{}", elapsed, sql.replaceAll("[\\s]+", " "));
}
return StrUtil.format("耗時:{}ms,Sql:{}",
elapsed,
category.equals("commit") || category.equals("rollback") ? category :
sql.replaceAll("[\\s]+", " "));
}
}
formatMessage方法提供了7個參數(shù),這7個參數(shù)大家可以自由組合,其中常用的參數(shù)有elapsed和sql。

簡單介紹一下這7個參數(shù)的具體含義:
connectionId:當(dāng)前connection的id。
now:當(dāng)前時間,毫秒值。
elapsed:sql執(zhí)行的耗時。需要注意的是這里的耗時指的是從發(fā)送sql到服務(wù)器截止到收到服務(wù)器響應(yīng)結(jié)果的總耗時,而不是sql本身在服務(wù)器的執(zhí)行時間。
category:操作的類型,比如查詢,更新,commit,rollback等。
prepared:編譯后的sql,不打印具體的參數(shù)。
sql:具體的執(zhí)行sql,參數(shù)占位符會被真正的參數(shù)值替換。
url:當(dāng)前的數(shù)據(jù)庫連接。
效果
經(jīng)過以上4個步驟,P6Spy就可以幫助我們自動打印執(zhí)行的sql了。我們新建一個controller看看具體的效果。
@RestController
@RequestMapping(value = "/demo")
public class DemoController {
@Resource
private UserRepository userRepository;
@RequestMapping(value = "/test")
public ResponseEntity<Object> test() {
User user = new User();
user.setName("張三");
user.setPwd("123");
User save = this.userRepository.save(user);
System.out.println(JSON.toJSONString(save));
Iterable<User> all = this.userRepository.findAll();
all.forEach(value -> System.out.println(JSON.toJSONString(value)));
return ResponseEntity.ok().build();
}
}
啟動項(xiàng)目,訪問
http://localhost:8080/demo/test,看一下控制臺的打印效果:

與Mybatis Plus的集成
由于很多的Spring項(xiàng)目也使用了Mybatis Plus,所以MP也提供了對P6Spy的支持。使用方式也很簡單,只需對步驟二稍微做一點(diǎn)修改即可。
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/db
username: username
password: pwd123456
driver-class-name: com.mysql.cj.jdbc.Driver
//增加一個配置即可使用p6spy的功能
p6spy:true
最后
P6Spy通過對數(shù)據(jù)源的包裝,進(jìn)而實(shí)現(xiàn)了一系列的功能增強(qiáng),讓我們可以方便的打印sql執(zhí)行情況。但是相應(yīng)的,如果在生產(chǎn)環(huán)境開啟p6spy的打印功能,對性能還是由一定的影響的。
建議大家在測試環(huán)境開啟此功能,對跟蹤、修復(fù)bug都很有幫助。