1. 概述

在这个教程中,我们将讨论P6Spy,一个开源的、用于Java应用程序中拦截SQL日志的免费库。

在文章的第一部分,我们将探讨使用这个外部库而非仅仅启用JPA或Hibernate的SQL日志记录的主要优势,以及我们如何将该库集成到我们的应用中。接着,我们将展示一个简单的例子,说明如何使用P6Spy与Spring Boot应用一起工作,以展示一些重要的配置选项。

2. P6Spy 安装

P6Spy需要在应用服务器上安装。通常,只需将应用JAR添加到类路径中,并适当地配置驱动程序和JDBC连接即可。

另一种使用P6Spy的方式是通过修改现有应用程序代码,假设进行小量代码更改是可以接受的。在下一节中,我们将看到如何通过依赖自动配置在Spring Boot应用中集成P6Spy的一个示例。

p6spy-spring-boot-starter是一个提供P6Spy和其他数据库监控库集成的存储库。通过这个库,启用P6Spy的日志记录非常简单,只需要在类路径上添加一个jar文件。使用Maven,只需在POM.xml中添加以下内容:

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

如果我们想要配置日志,需要在资源文件夹中添加一个名为“spy.properties”的文件:

appender=com.p6spy.engine.spy.appender.FileLogger
logfile=database.log
append=true
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat=%(currentTime)|%(executionTime)|%(category)|%(sqlSingleLine)

在这个例子中,我们已配置P6Spy以以追加模式将信息写入名为“database.log”的自定义格式文件。这只是其中的一些配置,更多详细信息可以在项目网站上找到。

3. 日志示例

为了查看日志,我们需要运行一些查询。让我们在应用中添加几个简单的端点:

@RestController
@RequestMapping("student")
public class StudentController {
    @Autowired
    private StudentRepository repository;
    @RequestMapping("/save")
    public Long save() {
        return repository.save(new Student("Pablo", "Picasso")).getId();
    }
    @RequestMapping("/find/{name}")
    public List<Student> getAll(@PathVariable String name) {
        return repository.findAllByFirstName(name);
    }
}

假设应用暴露在8080端口上,现在我们可以使用CURL来访问这些端点:

curl http://localhost:8080/student/save
curl http://localhost:8080/student/find/Pablo

现在我们可以看到,项目目录下已创建了一个名为“database.log”的文件,其内容如下:

1683396972301|0|statement|select next value for student_seq
1683396972318|0|statement|insert into student (first_name, last_name, id) values ('Pablo', 'Picasso', 1)
1683396972320|0|commit|
1683396990989|0|statement|select s1_0.id,s1_0.first_name,s1_0.last_name from student s1_0 where s1_0.first_name='Pablo'

如果使用预编译语句,并手动管理提交和回滚,日志记录也会起作用。让我们在应用中添加另一个控制器来测试此行为:

@RestController
@RequestMapping("jdbc")
public class JDBCController {
    @Autowired
    private DataSource dataSource;

    @RequestMapping("/commit")
    public List<Map<String, String>> select() {
        List<Map<String, String>> results = new ArrayList<>();
        try {
            Connection connection = dataSource.getConnection();
            Statement statement = connection.createStatement();
            statement.executeQuery("SELECT * FROM student");
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        return results;
    }

    @RequestMapping("/rollback")
    public List<Map<String, String>> rollback() {
        List<Map<String, String>> results = new ArrayList<>();
        try (Connection connection = dataSource.getConnection()) {
            connection.rollback();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
        return results;
    }

    @RequestMapping("/query-error")
    public void error() {
        try (Connection connection = dataSource.getConnection();
             Statement statement = connection.createStatement()) {
            statement.execute("SELECT UNDEFINED()");
        } catch (Exception ignored) {
        }
    }
}

然后,通过CURL请求访问以下端点:

curl http://localhost:8080/jdbc/commit
curl http://localhost:8080/jdbc/rollback
curl http://localhost:8080/jdbc/query-error

结果,我们会在“database.log”文件中看到以下日志条目:

1683448381083|0|statement|SELECT * FROM student
1683448381087|0|commit|
1683448386586|0|rollback|
1683448388604|3|statement|SELECT UNDEFINED()

4. 总结

在这篇文章中,我们看到了依赖像P6Spy这样的第三方库来记录数据库查询的多个优势。例如,我们尝试的特定配置可以避免“吵闹邻居”问题(想象一下满是查询的日志控制台)。

如往常一样,完整的代码可在GitHub上找到。