使用Spring Data Cassandra记录查询

2023/05/11

1. 概述

Apache Cassandra是一个可扩展的分布式NoSQL数据库。Cassandra在节点之间流式传输数据,并提供无单点故障的连续可用性。事实上,Cassandra能够以卓越的性能处理大量数据。

在开发使用数据库的应用程序时,能够记录和调试已执行的查询非常重要。在本教程中,我们将研究在将Apache Cassandra与Spring Boot结合使用时如何记录查询和语句。

在我们的示例中,我们将使用Spring Data Repository抽象和Testcontainers库。我们将看到如何通过Spring配置来配置Cassandra查询日志记录。此外,我们将探索Datastax请求记录器。我们可以配置此内置组件以进行更高级的日志记录。

2. 搭建测试环境

为了演示查询日志记录,我们需要设置一个测试环境。首先,我们将使用Spring Data Apache Cassandra设置测试数据。接下来,我们将使用Testcontainers库来运行Cassandra数据库容器以进行集成测试。

2.1 Cassandra Repository

Spring Data使我们能够基于通用的Spring接口创建Cassandra Repository。首先,让我们从定义一个简单的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接口为我们的DAO定义一个Spring Data Repository:

@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

因此,Spring Data会自动为我们创建带注解的表。

我们应该注意,不建议将create_if_not_exists选项用于生产系统。

作为替代方案,可以通过从标准根类路径加载schema.sql脚本来创建表。

2.2 Cassandra容器

下一步,让我们在特定端口上配置和公开Cassandra容器:

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

在使用容器进行集成测试之前,我们需要覆盖Spring Data建立连接所需的测试属性

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键空间。键空间类似于RDBMS中的数据库。

2.3 集成测试

现在,我们已经准备就绪,可以开始编写集成测试了。

我们对记录选择、插入和删除查询感兴趣。因此,我们将编写几个测试来触发这些不同类型的查询。

首先,我们将编写一个用于保存和更新Person的测试。我们希望此测试执行两次插入和一次选择数据库查询:

@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");
}

然后,我们将编写一个用于保存和删除现有Person的测试。我们希望此测试执行一次插入、删除和选择数据库查询:

@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 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 (?,?,?,?,?,?)]
2021-09-25 12:42:01.279 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@539dd2d0
2021-09-25 12:42:01.290 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing prepared statement [INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate) VALUES (?,?,?,?,?,?)]
2021-09-25 12:42:01.351 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Preparing statement [SELECT * FROM person WHERE id IN (371bb4a0-1ded-11ec-8cad-934f1aec79e6)]
  using org.springframework.data.cassandra.core.CassandraTemplate$PreparedStatementHandler@3e61cffd
2021-09-25 12:42:01.370 DEBUG 17856 --- [           main] o.s.data.cassandra.core.cql.CqlTemplate:
  Executing prepared statement [SELECT * FROM person WHERE id IN (371bb4a0-1ded-11ec-8cad-934f1aec79e6)]

不幸的是,使用这个解决方案,我们将看不到语句中使用的绑定值的输出。

4. Datastax请求跟踪器

DataStax请求跟踪器是一个会话范围的组件,用于接收有关每个Cassandra请求结果的通知

Apache Cassandra的DataStax Java驱动程序带有一个可选的请求跟踪器实现,用于记录所有请求。

4.1 Noop请求跟踪器

默认请求跟踪器实现称为NoopRequestTracker。因此,它什么都不做:

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

要设置不同的跟踪器,我们应该在Cassandra配置中或通过系统属性指定一个实现RequestTracker的类。

4.2 请求记录器

RequestLogger是RequestTracker的内置实现,用于记录每个请求

我们可以通过设置特定的DataStax Java驱动程序系统属性来启用它:

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]
2021-09-25 13:06:31.811  INFO 11172 --- [        s0-io-4] c.d.o.d.i.core.tracker.RequestLogger:
  [s0|778232359][Node(endPoint=localhost/[0:0:0:0:0:0:0:1]:49281, hostId=c50413d5-03b6-4037-9c46-29f0c0da595a, hashCode=68c305fe)]
  Success (4 ms) [6 values] INSERT INTO person (birthdate,firstname,id,lastname,lastpurchaseddate,lastvisiteddate)
  VALUES (?,?,?,?,?,?) [birthdate=NULL, firstname='Marko', id=a3ad6890-1df0-11ec-a295-7d319da1858a, lastname='Modric', lastpurchaseddate=NULL, lastvisiteddate=NULL]
2021-09-25 13:06:31.847  INFO 11172 --- [        s0-io-4] c.d.o.d.i.core.tracker.RequestLogger:
  [s0|1947131919][Node(endPoint=localhost/[0:0:0:0:0:0:0:1]:49281, hostId=c50413d5-03b6-4037-9c46-29f0c0da595a, hashCode=68c305fe)]
  Success (5 ms) [0 values] SELECT * FROM person WHERE id IN (a3ad6890-1df0-11ec-a295-7d319da1858a)

我们将看到所有请求都记录在com.datastax.oss.driver.internal.core.tracker.RequestLogger类下。

此外,语句中使用的所有绑定值也会默认记录下来

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 其他选项

在我们的第一个示例中,我们启用了慢速请求的日志记录。我们可以使用threshold属性将成功的请求归类为慢

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. 总结

在本文中,我们探讨了将Apache Cassandra与Spring Boot结合使用时查询和语句的日志记录。

在示例中,我们介绍了在Spring Data Apache Cassandra中配置日志级别。我们看到Spring Data会记录查询,但不会记录绑定值。最后,我们探索了Datastax请求跟踪器。它是一个高度可定制的组件,我们可以使用它来记录Cassandra查询及其绑定值。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章