最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

在SpringBoot中使用P6Spy攔截SQL語句的完整指南

 更新時間:2025年11月04日 10:20:15   作者:磊磊落落  
P6Spy 是一個用于攔截和記錄應(yīng)用程序和數(shù)據(jù)庫之間的所有 JDBC 操作的開源 Java 庫,本文先簡單介紹一下 P6Spy 的工作原理,然后以在 Spring Boot 中使用 P6Spy 攔截 SQL 語句為例來演示 P6Spy 的基本功能和使用方式,需要的朋友可以參考下

引言

P6Spy 是一個用于攔截和記錄應(yīng)用程序和數(shù)據(jù)庫之間的所有 JDBC 操作的開源 Java 庫。P6Spy 是無代碼侵入的,也就是說,我們無需修改應(yīng)用程序代碼,只需做一些簡單的配置,即可使用 P6Spy 這個橫在應(yīng)用程序和 JDBC 驅(qū)動之間的「間諜」來捕獲所有使用的 SQL 語句和其執(zhí)行細(xì)節(jié)。

因 P6Spy 是在 JDBC 層工作,所以不論我們使用的是市面上哪一款基于 JDBC 之上的 ORM 框架(如:Hibernate、MyBatis),P6Spy 都能很好的捕捉到最終生成的 SQL。

有了 P6Spy 這個工具,我們就可以很好的找出應(yīng)用程序中的慢查詢 SQL 語句、高頻 SQL 語句,從而為數(shù)據(jù)庫優(yōu)化提供數(shù)據(jù)支持。此外,P6Spy 還適用于安全審計或數(shù)據(jù)變更追蹤等場景。

本文先簡單介紹一下 P6Spy 的工作原理,然后以在 Spring Boot 中使用 P6Spy 攔截 SQL 語句為例來演示 P6Spy 的基本功能和使用方式。

1 P6Spy 工作原理

正如本文開頭所提到的,P6Spy 在 JDBC 層工作,像一個間諜一樣「劫持」了 DriverManager,將真實的 JDBC 連接(如:jdbc:mysql//localhost/db)替換為了 P6Spy 的連接(如:jdbc:p6spy:mysql//localhost/db);并將真實的驅(qū)動類(如:com.mysql.cj.jdbc.Driver)替換為了 P6Spy 的驅(qū)動類(com.p6spy.engine.spy.P6SpyDriver)。這樣,應(yīng)用程序針對數(shù)據(jù)庫的所有調(diào)用,都會被 P6Spy 所攔截和處理,所以 P6Spy 可以很容易的捕獲到執(zhí)行的 SQL、耗時情況等信息。

2 在 Spring Boot 中使用 P6Spy

下面,嘗試在 Spring Boot 中使用 P6Spy,本文示例工程使用的 ORM 框架是 JPA,使用的數(shù)據(jù)庫為 MySQL。

寫作本文時,使用的 Java、Spring Boot、P6Spy 的版本為:

Java: 17
Spring Boot: 3.5.5
P6Spy: 3.9.1

2.1 引入 Maven 依賴

在 Spring Boot 中使用 P6Spy 非常的簡單,只需引入一個 p6spy-spring-boot-starter 依賴即可。下面即是我們的示例工程用到的主要依賴,有 JPA、Lombok、MySQL Connector 和 P6Spy Starter。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.38</version>
        <scope>provided</scope>
    </dependency>

    <!-- driver -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>9.4.0</version>
        <scope>runtime</scope>
    </dependency>

    <!-- p6spy -->
    <dependency>
        <groupId>com.github.gavlyukovskiy</groupId>
        <artifactId>p6spy-spring-boot-starter</artifactId>
        <version>1.12.0</version>
    </dependency>
</dependencies>

2.2 建表、建 Entity、建 Repository

本示例工程使用的數(shù)據(jù)庫為 MySQL,我們希望 P6Spy 幫助捕獲 User 的增刪改查操作,所以需要建一個 user 表:

DROP TABLE IF EXISTS user;
CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(20) NOT NULL,
    email VARCHAR(20) NOT NULL,
    year_of_birth INT NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT '2025-01-01 00:00:00',
    updated_at TIMESTAMP NOT NULL DEFAULT '2025-01-01 00:00:00'
);

對應(yīng) user 表的 Java Entity 為 User,其源碼如下:

package com.example.demo.model;

@Data
@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
    private Integer yearOfBirth;
    private Date createdAt;
    private Date updatedAt;
}

我們使用的 ORM 框架為 JPA,為了支撐 User 的增刪改查操作,需要編寫一個 UserRepository。

可以看到,我們在如下 UserRepository 編寫了四個方法,前兩個為查詢,后兩個為更新和刪除(分別使用 HQL 和原生 SQL)。

package com.example.demo.repository;

public interface UserRepository extends CrudRepository<User, Long> {

    boolean existsByNameAndEmail(String name, String email);

    List<User> findByNameIgnoreCase(String name);

    @Transactional
    @Modifying
    @Query("UPDATE User u SET u.name = :name WHERE u.email = :email")
    void updateNameByEmail(@Param("name") String name, @Param("email") String email);

    @Transactional
    @Modifying
    @Query(value = "DELETE FROM user WHERE email = :email", nativeQuery = true)
    void deleteByEmail(@Param("email") String email);
}

2.3 添加配置

下面在 application.yaml 添加數(shù)據(jù)庫連接配置。

可以看到,我們并未手動將數(shù)據(jù)庫 URL 更改為 jdbc:p6spy:mysql://xxx,也未手動將 Driver Class 更改為 com.p6spy.engine.spy.P6SpyDriver,完全使用的是實際的數(shù)據(jù)庫 URL 和驅(qū)動類。這是因為,我們引入的不是原始的 P6Spy 依賴包,而是 P6Spy Spring Boot Starter,該 Starter 內(nèi)部會使用裝飾器幫我們修改數(shù)據(jù)源,從而省去了手動的修改。

# application.yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    show-sql: false

2.4 P6Spy 初步使用

接下來就可以對 P6Spy 進(jìn)行使用了。下面我們編寫一個單元測試類 UserRepositoryTest 來對上述 UserRepository 進(jìn)行測試。

package com.example.demo.repository;

@SpringBootTest
public class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    @Order(1)
    public void testSave() {
        User user = new User();
        user.setName("Larry");
        user.setEmail("larry@larry.com");
        user.setYearOfBirth(2000);
        user.setCreatedAt(new Date());
        user.setUpdatedAt(new Date());

        userRepository.save(user);
    }

    // ...
}

調(diào)用后,發(fā)現(xiàn)應(yīng)用程序日志里多了如下一行輸出??梢钥吹?,P6Spy 起作用了,其捕獲了最終的 SQL 語句,并打印了該語句執(zhí)行所花費的時間。

#1756628295717 | took 29ms | statement | connection 2| url jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
insert into user (created_at,email,name,updated_at,year_of_birth) values (?,?,?,?,?)
insert into user (created_at,email,name,updated_at,year_of_birth) values ('2025-08-31T16:18:15.557+0800','larry@larry.com','Larry','2025-08-31T16:18:15.557+0800',2000);

至此,我們可以看到,在 Spring Boot 中使用 P6Spy 非常的簡單,只需引入一個 P6Spy Starter 即可,無需做額外配置,P6Spy 即可幫我們捕捉 SQL 并統(tǒng)計其耗時信息。

2.5 P6Spy 進(jìn)階使用

自定義日志輸出格式

從上面的輸出可以看到,P6Spy 默認(rèn)的輸出除了打印最終 SQL 外,還打印了 Prepared Statement、數(shù)據(jù)庫 URL 等其它信息。如果我們不想要 P6Spy 打印這些額外的信息,可以手動指定需要打印的信息。

要想自定義一些 P6Spy 的參數(shù),就需要在工程 resources 目錄下新建一個 spy.properties。

下面的配置在 spy.properties 中指定了自定義的日志輸出格式。

# spy.properties
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(currentTime)|%(executionTime)ms|%(category)|%(sqlSingleLine)

再次運行 UserRepositoryTest 測試類,發(fā)現(xiàn) P6Spy 的打印信息變了。

1756628485593|31ms|statement|insert into user (created_at,email,name,updated_at,year_of_birth) values ('2025-08-31T16:21:25.354+0800','larry@larry.com','Larry','2025-08-31T16:21:25.354+0800',2000)

自定義 P6Spy 打印器

再高級一點的用法是自己編寫 P6Spy 的「打印器」,下面嘗試編寫一個 P6Spy 的「打印器」,除了將時間戳顯示更直觀以外,還嘗試獲取并輸出 SQL 操作的調(diào)用者(Caller)。

下面的配置即在 spy.properties 指定了自己編寫的「打印器」。

# spy.properties
logMessageFormat=com.example.demo.formatter.CustomP6SpyMessageFormatter

該「打印器」的源碼如下:

package com.example.demo.formatter;

public class CustomP6SpyMessageFormatter implements MessageFormattingStrategy {

    @Override
    public String formatMessage(int connId, String now, long elapsed, String category, String prepared, String sql, String url) {
        if (StringUtils.isBlank(sql)) {
            return "";
        }

        String caller = Arrays.stream(Thread.currentThread().getStackTrace())
                .filter(e -> e.getClassName().startsWith("com.example.demo"))
                .filter(e -> !e.getClassName().equals(CustomP6SpyMessageFormatter.class.getName()))
                .findFirst()
                .map(e -> String.format("%s#%s:%d", e.getClassName(), e.getMethodName(), e.getLineNumber()))
                .orElse("Unknown");

        String nowFormatted = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
                .format(new Date(Long.parseLong(now)));

        return String.format("%s - [Time elapsed: %d ms] [Caller: %s] [SQL: %s]", nowFormatted, elapsed, caller, sql.trim());
    }
}

可以看到,該自定義「打印器」實現(xiàn)了 P6Spy 的 MessageFormattingStrategy 接口并重寫了其 formatMessage() 方法。然后,在該方法內(nèi),按照我們的想法,找出了調(diào)用者并格式化了 SQL 抓取時間。

重新運行 UserRepositoryTest 測試類,發(fā)現(xiàn) P6Spy 打印的日志信息變?yōu)榱宋覀兯远x的。

2025-08-31 16:25:09.685 - [Time elapsed: 31 ms] [Caller: com.example.demo.repository.UserRepositoryTest#testSave:27] [SQL: insert into user (created_at,email,name,updated_at,year_of_birth) values ('2025-08-31T16:25:09.483+0800','larry@larry.com','Larry','2025-08-31T16:25:09.483+0800',2000)]

將 P6Spy 日志與應(yīng)用程序日志分離

P6Spy 默認(rèn)使用的 Log Appender 是 com.p6spy.engine.spy.appender.Slf4JLogger。所以,P6Spy 的日志默認(rèn)是輸出到我們應(yīng)用程序的總?cè)罩局械摹?/p>

實際項目中,使用 P6Spy 時為了更好的觀察 SQL 的運行情況,通常需要將 P6Spy 的日志單獨打印到一個文件,并進(jìn)行單獨管理。

下面即嘗試修改我們示例工程的日志管理文件 logback.xml,將 P6Spy 的日志單獨輸出到一個文件 p6spy.log,并進(jìn)行存檔和定期清理。

<!-- logback.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    ...
    <appender name="P6SPY_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/p6spy.log</file>
        <encoder>
            <pattern>%msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/p6spy.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>7</maxHistory>
            <totalSizeCap>2GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <logger name="p6spy" level="INFO" additivity="false">
        <appender-ref ref="P6SPY_FILE"/>
        <appender-ref ref="CONSOLE"/>
    </logger>
    ...
</configuration>

這樣,P6Spy 的日志即會與應(yīng)用程序總?cè)罩痉蛛x,而單獨輸出到 p6spy.log,而且歷史日志也會按要求清理或存檔。

p6spy.log
p6spy.2025-08-30.0.log.gz

3 小結(jié)

綜上,我們首先介紹了 P6Spy 的功能、使用場景和工作原理,然后在 Spring Boot 示例工程中引入了 P6Spy Starter,對 P6Spy 基礎(chǔ)功能和進(jìn)階功能的使用進(jìn)行了演示。

以上就是在SpringBoot中使用P6Spy攔截SQL語句的完整指南的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot P6Spy攔截SQL語句的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java 中的抽象介紹

    Java 中的抽象介紹

    這篇文章主要介紹了Java 中的抽象,數(shù)據(jù)抽象是一種僅向用戶顯示基本細(xì)節(jié)的屬性。不向用戶顯示瑣碎或非必需的單元,下面文章Java抽象詳細(xì)內(nèi)容,需要的朋友可以參考一下
    2021-12-12
  • IDEA配置Vue項目啟動器的過程

    IDEA配置Vue項目啟動器的過程

    文章介紹了兩種啟動方式一是通過控制臺手動輸入指令并用Ctrl+C終止;二是配置npm啟動器以簡化測試流程,提升效率
    2025-07-07
  • java.lang.NullPointerException異常問題解決方案

    java.lang.NullPointerException異常問題解決方案

    這篇文章主要介紹了java.lang.NullPointerException異常問題解決方案,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • Java類的序列化版本唯一標(biāo)識符serialVersionUID使用

    Java類的序列化版本唯一標(biāo)識符serialVersionUID使用

    serialVersionUID是一個類的序列化版本唯一標(biāo)識符,用于確保在反序列化過程中類的實例與序列化文件中的類版本相匹配,它在版本兼容性和安全性方面起著關(guān)鍵作用
    2025-01-01
  • 老生常談java中的數(shù)組初始化

    老生常談java中的數(shù)組初始化

    下面小編就為大家?guī)硪黄仙U刯ava中的數(shù)組初始化。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • SpringBoot搭建Dubbo項目實現(xiàn)斐波那契第n項詳解

    SpringBoot搭建Dubbo項目實現(xiàn)斐波那契第n項詳解

    這篇文章主要講解了“SpringBoot+Dubbo怎么實現(xiàn)斐波那契第N項”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)吧
    2022-06-06
  • Java中joda日期格式化工具的使用示例

    Java中joda日期格式化工具的使用示例

    這篇文章主要介紹了Java中joda日期格式化工具的使用示例,幫助大家更好的利用Java處理時間,感興趣的朋友可以了解下
    2021-01-01
  • Spring中@Value注解的使用方法詳解

    Spring中@Value注解的使用方法詳解

    這篇文章主要介紹了Spring中@Value注解的使用方法詳解,在spring項目中必不可少的就是讀取配置文件,那么讀取配置文件就有兩種方式,一種就是使用Spring中@Value注解,還有一種是使用SpringBoot中的@ConfigurationProperties注解,需要的朋友可以參考下
    2024-01-01
  • Java8需要知道的4個函數(shù)式接口簡單教程

    Java8需要知道的4個函數(shù)式接口簡單教程

    這篇文章主要介紹了Java?8中引入的函數(shù)式接口,包括Consumer、Supplier、Predicate和Function,以及它們的用法和特點,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-03-03
  • Spring中的@ConditionalOnProperty作用和用法詳解

    Spring中的@ConditionalOnProperty作用和用法詳解

    這篇文章主要介紹了Spring中的@ConditionalOnProperty作用和用法詳解,在spring boot中有時候需要控制配置類是否生效,可以使用@ConditionalOnProperty注解來控制@Configuration是否生效,需要的朋友可以參考下
    2023-11-11

最新評論

嘉兴市| 江西省| 大宁县| 上思县| 沁阳市| 三明市| 海林市| 邹平县| 辽阳县| 通海县| 墨脱县| 太白县| 岐山县| 长岛县| 鹤岗市| 南华县| 镇赉县| 河北省| 宁阳县| 西贡区| 常宁市| 伽师县| 镇江市| 长葛市| 邢台市| 三门县| 华阴市| 江源县| 平南县| 通江县| 四子王旗| 吉木萨尔县| 盐津县| 肃南| 孟州市| 什邡市| 勐海县| 正安县| 西充县| 云安县| 广昌县|