1. Tổng quan

Spring JDBC và JPA cung cấp các API cho phép chúng ta làm việc với các API của JDBC mà không cần thông qua các câu lệnh SQL thuần tuý. Điều này rất tiện lợi khi các API này đảm nhận nhiệm vụ sinh ra các câu SQL thuần tuý tương ứng với từng database. Do vậy chúng ta chỉ cần triển khai các câu lệnh SQL 1 lần duy nhất thông qua các API của JPA và Spring JDBC.

Tiện lợi là vậy, nhưng đôi lúc những câu truy vấn hoạt động không như chúng ta mong muốn, và nhu cầu chúng ta cần phải xem các câu lệnh SQL thuần tuý bên dưới được sinh ra như thế này để có thể dễ dàng debug, hoạt có thể kiểm thử với những câu lệnh sql này.

Trong bài viết này, mình sẽ cung cấp một số cách để có thể xem những câu lệnh SQL thuần tuý được sinh ra trông như thế nào, vì mặc định chúng sẽ bị ẩn bởi Spring Boot.

Trong hướng dẫn ngắn này, chúng ta sẽ xem xét các cách khác nhau để ghi lại các câu lệnh SQL này trong Spring Boot.

2. Logging JPA Queries

2.1. To Standard Output

Cách đơn giản nhất để có thể xem các câu truy vấn xuất ra các thiết bị output như màn hình console v.v là thêm một thuộc tính vào file application.properties:

spring.jpa.show-sql=true

Để câu truy vấn xuất ra màn hình có định dạng rõ ràng và dễ nhìn hơn chúng ta thêm

spring.jpa.properties.hibernate.format_sql=true

Với cấu hình ở trên, log sẽ được in ra như thế này:

2024-03-26T23:30:42.680-04:00 DEBUG 9477 --- [main] org.hibernate.SQL:  
  select
    c1_0.id,
    c1_0.budget,
    c1_0.end_date,
    c1_0.name,
    c1_0.start_date  
  from
    campaign c1_0  
  where
    c1_0.start_date between ? and ?

Mặc dù cách này cực kỳ đơn giản, nhưng nó không được khuyến khích vì nó xuất trực tiếp các câu truy vấn ra các thiết bị output mà không có bất kỳ một sự tối ưu nào từ các logging framework.

Ngoài ra, đối với cách này thì các tham số đầu vào cho các câu truy vấn cũng không được xuất ra.

2.2. Sử dụng Logger

Bây giờ, hãy xem cách chúng ta có thể ghi các câu lệnh SQL bằng cách định cấu hình trình loggers trong file cấu hình application.properties:

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

Dòng đầu tiên sẽ ghi log các câu truy vấn SQL, dòng thứ hai sẽ ghi lại log các tham số của câu truy vấn sẽ được thêm vào.

Thuộc tính:

spring.jpa.properties.hibernate.format_sql=true

Cũng sẽ hoạt động cho các cấu hình này.

Bằng cách thiết lập các thuộc tính này, các thông tin về câu truy vấn sẽ được gửi đến các thư viện logger mà dự án đang sử dụng. Mặc định Spring Boot sử dụng logback là thư viện logger mặc định.

Nếu bạn muốn ghi log một câu truy vấn với các tham số ràng buộc, chúng ta có thể thêm một thuộc tính vào file application.properties:

logging.level.org.hibernate.orm.jdbc.bind=TRACE

Thuộc tính ở trên đặt mức độ ghi log cho hibernate thành TRACE cho JDBC, nó sẽ ghi ra chi tiết thông tin cùng với các tham số ràng buộc như sau:

org.hibernate.SQL : select c1_0.id,c1_0.budget,c1_0.end_date,c1_0.name,c1_0.start_date from campaign c1_0 where c1_0.start_date between ? and ?
org.hibernate.orm.jdbc.bind : binding parameter [1] as [DATE] - [2024-04-26]
org.hibernate.orm.jdbc.bind : binding parameter [2] as [DATE] - [2024-04-05]

3. Logging JdbcTemplate Queries

Để cấu hình ghi lại các câu lệnh SQL khi sử dụng JdbcTemplate, chúng ta cần hai thuộc tính sau:

logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG
logging.level.org.springframework.jdbc.core.StatementCreatorUtils=TRACE

Giống với cấu hình ghi log JPA, dòng đầu tiên dùng để ghi log các câu lệnh, dòng thứ hai dùng để ghi log các tham số của câu truy vấn được chuẩn bị. Với các cấu hình ở trên, log SQL sẽ được in ra cùng với các tham số như sau:

2024-03-26T23:45:44.505-04:00 DEBUG 18067 --- [main] o.s.jdbc.core.JdbcTemplate: Executing prepared SQL statement [SELECT id FROM CAMPAIGN WHERE name = ?]
2024-03-26T23:45:44.513-04:00 TRACE 18067 --- [main] o.s.jdbc.core.StatementCreatorUtils: Setting SQL statement parameter value: column index 1, parameter value [sdfse1], value class [java.lang.String], SQL type unknown

Giải thích thêm:

logging.level.org.springframework.jdbc.core.JdbcTemplate=DEBUG: "Dòng này thiết lập mức độ ghi log cho JdbcTemplate thành DEBUG, nghĩa là sẽ ghi log tất cả các câu truy vấn SQL được thực thi."
logging.level.org.springframework.jdbc.core.StatementCreatorUtils=TRACE: "Dòng này thiết lập mức độ ghi log cho StatementCreatorUtils thành TRACE, nghĩa là sẽ ghi log chi tiết hơn, bao gồm cả giá trị của các tham số ràng buộc trong câu truy vấn được chuẩn bị."

Trong log mẫu, bạn có thể thấy:

Dòng đầu tiên hiển thị câu truy vấn SQL đang được thực thi

(SELECT id FROM CAMPAIGN WHERE name = ?)

Dòng thứ hai hiển thị chi tiết về tham số ràng buộc

(name), bao gồm chỉ số cột (column index 1), giá trị tham số ([sdfse1]), kiểu dữ liệu của giá trị (java.lang.String), và kiểu dữ liệu SQL (không xác định trong trường hợp này).

4: Ghi log tất cả các loại câu truy vấn

Sử dụng interceptor là cách tốt nhất để ghi log tất cả các loại câu truy vấn SQL. Với cách tiếp cận này, chúng ta có thể can thiệp vào các lệnh gọi đến JDBC, định dạng chúng và sau đó ghi log các câu truy vấn SQL theo một định dạng tùy chỉnh.

Thư viện datasource-proxy là một trong những framework phổ biến can thiệp vào các câu truy vấn SQL và ghi lại log của chúng. Chúng ta cần thêm dependency của nó vào file pom.xml:

<dependency>
    <groupId>com.github.gavlyukovskiy</groupId>
    <artifactId>datasource-proxy-spring-boot-starter</artifactId>
    <version>1.9.1</version>
</dependency>

Ngoài ra, chúng ta cần cập nhật thuộc tính để bật ghi log cho datasource-proxy:

logging.level.net.ttddyy.dsproxy.listener=debug

Với thiết lập này, nó sẽ in ra một log đẹp hơn với các chi tiết như truy vấn và tham số, cùng những thông tin khác:

Name:dataSource, Connection:15, Time:1, Success:True
Type:Prepared, Batch:False, QuerySize:1, BatchSize:0
Query:["select c1_0.id,c1_0.budget,c1_0.end_date,c1_0.name,c1_0.start_date from campaign c1_0 where c1_0.start_date between ? and ?"]
Params:[(2024-04-26,2024-04-05)]

Lưu ý quan trọng:

Cách tiếp cận sử dụng interceptor ghi log tất cả các câu truy vấn từ JPA, JPQLprepared statements. Do đó, đây là cách tốt nhất để ghi log các câu truy vấn SQL với các tham số ràng buộc.

5: Cách Logging hoạt động?

Trong Spring/Hibernate, lớp tạo câu truy vấn SQL và các tham số đầu vào đã chứa sẵn mã để ghi log cho chúng. Tuy nhiên, log-level ghi log của các câu lệnh này được đặt thành DEBUGTRACE, thấp hơn mức mặc định trong Spring BootINFO.

Dó đó bằng cách thêm các cấu hình trên, chúng ta chỉ đơn giản là thiết lập mức độ ghi log cho các logger này thấp hơn tương ứng với log-level mà Hibernate sử dụng, lúc này chúng ta có thể thấy được các câu truy vấn được sinh ra

6: Kết luận

Trong bài viết ngắn này, chúng ta đã tìm hiểu một vài cách để ghi log các câu truy vấn SQL trong Spring Boot. Cũng như việc ghi log các tham số ràng buộc cho các câu truy vấn SQL. Cuối cùng, chúng ta đã thảo luận về lý do tại sao cách tiếp cận sử dụng interceptor là cách tốt nhất để ghi log các câu truy vấn SQL cùng với các tham số ràng buộc.

Nếu chúng ta chọn cấu hình nhiều appender (bộ nối), chúng ta cũng có thể tách riêng các câu lệnh SQL và các câu lệnh log khác vào các file log khác nhau để mọi thứ được rõ ràng.

link bài viết tham khảo