1. 概述

Apache Cassandra 是一款可扩展的分布式 NoSQL 数据库,通过节点间数据流实现持续可用性,且不存在单点故障。它能以卓越性能处理海量数据。

当开发数据库应用时,记录和调试查询语句至关重要。本文将探讨在 Spring Boot 中使用 Apache Cassandra 时如何记录查询和语句。

我们将使用 Spring Data 仓库抽象层和 Testcontainers 库,通过配置实现 Cassandra 查询日志记录,并深入探讨 Datastax 请求日志记录器的高级配置。

2. 测试环境搭建

2.1. Cassandra 仓库配置

Spring Data 允许我们基于通用接口创建 Cassandra 仓库。首先定义一个简单的 DAO 类:

@Table
public class Person {

    @PrimaryKey
    private UUID id;
    private String firstName;
    private String lastName;

    public Person(UUID id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // getters, setters, equals and hash code
}

然后通过继承 CassandraRepository 接口创建仓库:

@Repository
public interface PersonRepository extends CassandraRepository<Person, UUID> {}

application.properties 中添加以下配置:

spring.data.cassandra.schema-action=create_if_not_exists
spring.data.cassandra.local-datacenter=datacenter1

踩坑提示create_if_not_exists 不适用于生产环境,建议改用 schema.sql 脚本建表。

2.2. Cassandra 容器配置

使用 Testcontainers 暴露 Cassandra 容器端口:

@Container
public static final CassandraContainer cassandra = 
  (CassandraContainer) new CassandraContainer("cassandra:3.11.2").withExposedPorts(9042);

测试前需覆盖连接属性:

TestPropertyValues.of(
  "spring.data.cassandra.keyspace-name=" + KEYSPACE_NAME,
  "spring.data.cassandra.contact-points=" + cassandra.getContainerIpAddress(),
  "spring.data.cassandra.port=" + cassandra.getMappedPort(9042)
).applyTo(configurableApplicationContext.getEnvironment());

createKeyspace(cassandra.getCluster());

关键点:操作前必须创建 Cassandra Keyspace(类似 RDBMS 的数据库)。

2.3. 集成测试用例

编写测试触发不同类型查询:

更新记录测试(触发 2 次 INSERT + 1 次 SELECT):

@Test
void givenExistingPersonRecord_whenUpdatingIt_thenRecordIsUpdated() {
    UUID personId = UUIDs.timeBased();
    Person existingPerson = new Person(personId, "Luka", "Modric");
    personRepository.save(existingPerson);
    existingPerson.setFirstName("Marko");
    personRepository.save(existingPerson);

    List<Person> savedPersons = personRepository.findAllById(List.of(personId));
    assertThat(savedPersons.get(0).getFirstName()).isEqualTo("Marko");
}

删除记录测试(触发 1 次 INSERT + 1 次 DELETE + 1 次 SELECT):

@Test
void givenExistingPersonRecord_whenDeletingIt_thenRecordIsDeleted() {
    UUID personId = UUIDs.timeBased();
    Person existingPerson = new Person(personId, "Luka", "Modric");

    personRepository.delete(existingPerson);

    List<Person> savedPersons = personRepository.findAllById(List.of(personId));
    assertThat(savedPersons.isEmpty()).isTrue();
}

默认情况下,控制台不会打印任何查询日志。

3. Spring Data CQL 日志配置

Spring Data for Apache Cassandra 2.0+ 版本支持通过 application.properties 设置 CqlTemplate 日志级别:

logging.level.org.springframework.data.cassandra.core.cql.CqlTemplate=DEBUG

启用 DEBUG 级别后,日志会记录所有执行的查询和预处理语句:

2021-09-25 12:41:58.679 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing CQL statement [CREATE TABLE IF NOT EXISTS person
  (birthdate date, firstname text, id uuid, lastname text, lastpurchaseddate timestamp, lastvisiteddate timestamp, PRIMARY KEY (id));]
2021-09-25 12:42:01.204 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Preparing statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?)] using org.springframework.data.cassandra.core.CassandraTemplate$PreparedStatementHandler@4d16975b
2021-09-25 12:42:01.253 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing prepared statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate) VALUES (?,?,?,?,?,?)]

缺陷:此方式无法记录绑定参数的具体值。

4. Datastax 请求追踪器

DataStax 请求追踪器是会话级别的组件,能监控每个 Cassandra 请求的执行结果。其 Java 驱动提供了可选的请求日志实现。

4.1. 空操作追踪器

默认实现 NoopRequestTracker 不执行任何操作:

System.setProperty("datastax-java-driver.advanced.request-tracker.class", "NoopRequestTracker");

要切换追踪器,需在配置或系统属性中指定实现 RequestTracker 接口的类。

4.2. 请求日志记录器

RequestLogger 是内置实现,记录所有请求。通过系统属性启用:

System.setProperty("datastax-java-driver.advanced.request-tracker.class", "RequestLogger");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.success.enabled", "true");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.slow.enabled", "true");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.error.enabled", "true");

启用后日志会记录所有成功、慢速和失败的请求:

2021-09-25 13:06:31.799  INFO 11172 --- [        s0-io-4] c.d.o.d.i.core.tracker.RequestLogger:
  [s0|90232530][Node(endPoint=localhost/[0:0:0:0:0:0:0:1]:49281, hostId=c50413d5-03b6-4037-9c46-29f0c0da595a, hashCode=68c305fe)]
  Success (6 ms) [6 values] INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?) [birthdate=NULL, firstname='Luka', id=a3ad6890-1df0-11ec-a295-7d319da1858a, lastname='Modric', lastpurchaseddate=NULL, lastvisiteddate=NULL]

优势:默认会记录所有绑定参数的具体值。

4.3. 绑定参数控制

RequestLogger 支持高度自定义绑定参数输出:

System.setProperty("datastax-java-driver.advanced.request-tracker.logs.show-values", "true");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.max-value-length", "100");
System.setProperty("datastax-java-driver.advanced.request-tracker.logs.max-values", "100");

配置说明:

  • max-value-length:参数值最大显示长度(超长截断)
  • max-values:最多显示的参数数量

4.4. 高级选项

慢查询阈值:将成功请求归类为慢查询的时间阈值:

System.setProperty("datastax-java-driver.advanced.request-tracker.logs.slow.threshold ", "1 second");

堆栈跟踪控制:失败请求是否记录堆栈信息:

System.setProperty("datastax-java-driver.advanced.request-tracker.logs.show-stack-trace", "true");

日志级别规则:

  • 成功/慢速请求:INFO 级别
  • 失败请求:ERROR 级别

5. 总结

本文探讨了在 Spring Boot 中使用 Apache Cassandra 时的查询日志记录方案:

  1. Spring Data 方案:简单配置 CqlTemplate 日志级别,但无法记录绑定参数
  2. Datastax 方案:通过请求追踪器实现完整日志记录,支持:
    • 绑定参数输出控制
    • 慢查询阈值设置
    • 堆栈跟踪管理
    • 多维度日志分类

简单粗暴建议:生产环境推荐使用 Datastax 方案,其日志完整性和可定制性远超 Spring Data 基础方案。

完整源码请参考 GitHub 项目


原始标题:Logging Queries with Spring Data Cassandra