Spring Data Lovelace 中 Redis 和 Apache Cassandra 的新特性

工程 | Mark Paluch | 2018年9月26日 | ...

这篇博文介绍了Spring Data LovelaceApache CassandraRedis的新特性和值得关注的点。请务必查看关于Spring Data Lovelace 中 MongoDB 的新特性的博文。

随着 Spring Data Lovelace 上周发布了其正式版,现在是时候简要了解一下我们添加的新功能了。该版本包含了许多功能。

在这篇博文中,我将介绍Apache CassandraRedis

Spring Data for Apache Cassandra

在这个版本中,我们改进了对 Cassandra 特定类型的访问,引入了对生命周期事件的支持,改进了 Java 和 Kotlin 使用的编程体验,并包含了其他各种改进。让我们看看这个版本如何帮助改善您对 Cassandra 的数据访问。

Map 和 Tuple 的改进

Map 和 Tuple 数据类型是 Cassandra 中的特定类型,允许在单个列中存储多个值。以前,我们在映射实体中以原始形式支持这两种类型,这意味着您只能使用具有基本键和值的 Map。对于 Tuple,您只能使用 TupleValue(Cassandra 驱动程序的原始类型),而没有进一步的映射或模式支持。

在这个版本中,我们添加了对 Map 和 Tuple 类型属性的映射和转换支持。Map 现在可以包含非基本键和值,转换层会应用可能已注册的转换器。

考虑以下类型

@UserDefinedType
class Manufacturer {

  String name;

  // getters/setters omitted
}

@Table
class Supplier {

  Map<Manufacturer, List<String>> acceptedCurrencies;

  // getters/setters omitted
}

Manufacturer 是一个映射的用户定义类型,Map 使用它作为键。值表示为字符串的 List。我们现在可以重构代码以在列表中使用正确的 Currency 类型(例如 java.util.Currency)。为此,我们提供 StringCurrency 之间的转换器,并通过 CassandraCustomConversions 注册这些转换器。以下示例显示了如何执行此操作

enum StringToCurrencyConverter implements Converter<String, Currency> {
  INSTANCE;

  @Override
  public Currency convert(String source) {
    return Currency.getInstance(source);
  }
}

enum CurrencyToStringConverter implements Converter<Currency, String> {

  INSTANCE;

  @Override
  public String convert(Currency source) {
    return source.getCurrencyCode();
  }
}

@Configuration
class MyCassandraConfiguration {

  public CassandraCustomConversions cassandraCustomConversions() {
    return new CassandraCustomConversions(
      Arrays.asList(StringToCurrencyConverter.INSTANCE, CurrencyToStringConverter.INSTANCE));
  }
}

注册转换器后,我们可以继续在 Supplier 类型中使用 Currency,以便使用值对象而不是基本类型,如下例所示

@Table
class Supplier {

  Map<Manufacturer, List<Currency>> acceptedCurrencies;

  // getters/setters omitted
}

在以前的 Spring Data for Apache Cassandra 版本中,Tuple 实际上不可用。使用 Tuple 需要直接与 Row 交互并检索 TupleType 以创建相应的 Tuple 值。因此,我们决定提供映射的 Tuple 类型,如下例所示

@Table
class Supplier {

  List<Dependance> dependances;

  // getters/setters omitted
}

@Tuple
class Dependance {

  @Element(0) String address;
  @Element(1) String city;
  @Element(2) Currency currency;

  // getters/setters omitted
}

映射的 Tuple 使用 @Tuple 进行注释,Tuple 的各个组件通过(使用 @Element(…))引用其在 Tuple 中的序号索引。转换器检查加载的 Tuple 并将其映射到您域模型中的一部分常规 Java 类。您不再需要直接与 TupleTypeTupleValue 交互——尽管您仍然可以——但您可以通过类型安全的方式表示 Tuple 值。映射的 Tuple 受益于转换器的各种映射功能,并且可以引用已注册自定义转换器的类型。

对 Map 和 Tuple 的支持还包括模式生成,以便通过从域模型派生类型来快速设置模式。

有关更多详细信息,请参阅我们的映射 Tuple 示例

生命周期事件

Cassandra 映射框架现在包含几个 org.springframework.context.ApplicationEvent 事件,您的应用程序可以通过在 ApplicationContext 中注册特殊的 Bean 来响应这些事件。要拦截对象在经过转换过程(将您的域对象转换为 Statement)之前,您可以注册 AbstractCassandraEventListener 的子类,该子类覆盖 onBeforeSave 方法。当事件被分派时,您的侦听器会被调用并传递域对象,然后再进入转换器。以下示例显示了如何使用 onBeforeSave

public class BeforeConvertListener extends AbstractCassandraEventListener<Person> {
  @Override
  public void onBeforeSave(BeforeSaveEvent<Person> event) {
    // does some auditing manipulation, set timestamps, whatever
  }
}

在您的 Spring ApplicationContext 中声明这些 Bean 会导致它们在每次事件被分派时被调用。

以下回调方法存在于 AbstractCassandraEventListener

  • onBeforeSave:在 CassandraTemplate save 操作中 **在** 将行插入或保存到数据库 **之前** 调用。

  • onAfterSave:在 CassandraTemplate save 操作中 **在** 将行插入或保存到数据库 **之后** 调用。

  • onBeforeDelete:在 CassandraTemplate delete 操作中 **在** 从数据库中删除行 **之前** 调用。

  • onAfterDelete:在 CassandraTemplate delete 操作中 **在** 从数据库中删除行 **之后** 调用。

  • onAfterLoad:在 CassandraTemplate selectselectOne 方法中 **在** 从数据库中检索行 **之后** 调用。

  • onAfterConvert:在 CassandraTemplate selectselectOne 方法中 **在** 从数据库中检索的行被转换为 POJO **之后** 调用。

生命周期事件仅针对根级类型发出。用作实体根中属性的复杂类型不受事件发布的影响。

请参阅我们的生命周期事件示例

Kotlin 扩展

Spring Data 公开了接受目标类型的方法,以查询或将结果值投影到目标类型上。Kotlin 使用其自己的类型(KClass)表示类,这在尝试获取 Java Class 类型时可能是一个障碍。

Spring Data for Apache Cassandra 附带扩展,这些扩展为接受类型参数的方法添加了重载,方法是使用泛型或直接接受 KClass,如下例所示

operations.getTableName<Person>()

operations.getTableName(Person::class)

operations.find<Person>().as<Contact>
  .matching(query(where("firstname").isEqualTo("luke"))).all();

有关更多详细信息,请参阅我们的Cassandra Kotlin 使用示例。

流畅模板 API

CassandraOperations 接口是与 Apache Cassandra 进行更低级交互的核心组件之一。它提供了广泛的方法,涵盖了从批处理和结果流到 CRUD 操作的需求。您可以为每种方法找到多个重载。它们中的大多数涵盖了 API 的可选或替代部分,例如通过 CQL、Statement 查询,或通过 Query 查询。

FluentCassandraOperations 提供了更窄的接口,用于 CassandraOperations 的常用方法,并提供更易读、更流畅的 API。入口点 (insert(…)query(…)update(…) 等) 遵循基于要运行的操作的自然命名方案。从入口点开始,API 旨在仅提供上下文相关的、可导致调用实际 Cassandra 对应方法的终止方法。

考虑一个查询示例

List<Person> all = operations.query(Person.class)
  .inTable("people")
  .all();

此查询查询 people 表中的所有行,并将结果映射到 Person 类型。省略 inTable(…) 会从实体类型派生表名。

下一个示例使用投影和查询

List<Contact> all = operations.query(Person.class)
  .as(Contact.class)
  .matching(query(where("firstname").is("luke")))
  .all();

此查询使用映射 Person 类型的表,并将结果(DTO 或接口投影)投影到 Contact 上。查询本身是通过使用 Person 类型的字段名来映射的。您可以通过终止方法在检索单个实体和检索多个对象(作为 ListStream)之间切换:first()one()all()stream()

流畅的 API 是类型安全的,中间对象是不可变的。您可以准备查询的基本部分,然后继续进行更具体的执行,如下例所示

TerminatingSelect<Contact> select = operations.query(Person.class)
  .as(Contact.class)
  .matching(query(where("firstname").is("luke")))

Contact contact = select.first();
long count = select.count();

有关更多详细信息,请参阅我们的 Kotlin 示例

Spring Data for Apache Cassandra 模块中还添加了一些其他增强功能,因此请务必查看参考文档中的 新功能 部分,以了解有关反应式切片查询和 exists/count 投影的更多信息。

Spring Data Redis

此版本的 Spring Data Redis 附带了跨各种主题的改进,这些改进不适合 2.0 版本。其中大多数都消除了 Redis 集群使用方面的不足。核心主题是

  • 连接改进

  • Redis 集群使用的改进

  • 框架中的各种改进

连接改进

Redis 支持各种操作模式:独立模式、带复制的独立模式、带或不带复制的 Redis Sentinel、Redis 集群。我们已经涵盖了独立模式、Redis Sentinel 和 Redis 集群模式。到目前为止,缺少的部分是从副本读取数据。此版本引入了对各种 Redis 操作模式下副本读取的支持。以下示例显示了如何使用此新功能

LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
  .readFrom(ReadFrom.NEAREST)
  .build();

RedisSentinelConfiguration endpoint = new RedisSentinelConfiguration()
  .master("my-master")
  .sentinel("sentinel-host1", 26379)
  .sentinel("sentinel-host2", 26379);

LettuceConnectionFactory factory = new LettuceConnectionFactory(endpoint, clientConfiguration);

指定 ReadFrom 允许您在发出只读命令(如 GETSMEMBERS)时选择特定的节点类型。您可以使用 Lettuce 的预定义设置之一,也可以创建一个新的 ReadFrom 策略。在所有可使用副本的设置中都会考虑 ReadFrom:Redis Sentinel、Redis 集群和静态主/从设置(例如 AWS ElastiCache),这使我们引入了下一个改进。

您可以使用 AWS ElastiCache 或任何其他静态主/从设置(即,使用 Redis 和一个或多个专用副本)与 Spring Data Redis 和 Lettuce 一起从副本节点读取数据。在以前的版本中,您只能使用主节点。请查看以下配置代码片段

LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder().readFrom(ReadFrom.NEAREST).build();

RedisStaticMasterSlaveConfiguration endpoint = new RedisStaticMasterSlaveConfiguration("my-master-host", 6379)
  .node("my-replica-host1", 6379)
  .node("my-replica-host2", 6379);

LettuceConnectionFactory factory = new LettuceConnectionFactory(endpoint, clientConfiguration);

在此代码中,我们配置 LettuceConnectionFactory 以使用多个节点,而无需实际指定角色。Lettuce 本身会确定各个主机的角色,并根据其角色使用这些节点。

此类别中的最后一个改进是通过 Unix 域套接字使用本地连接。Unix 域套接字或 IPC(进程间通信)套接字是用于交换在同一主机操作系统上运行的进程之间数据的通信端点。与命名管道一样,Unix 域套接字支持传输与 TCP 相比的可靠字节流。由于 Unix 域套接字通信仅在内核中发生,因此通信绕过了网络,并且通常具有改进的性能配置文件。

要使用 Unix 域套接字,您需要使用 Lettuce 并为 Netty 添加本机扩展(在 Linux 上运行时使用 netty-transport-native-epoll,在 MacOS 上运行时使用 netty-transport-native-kqueue)。以下示例配置了通过套接字与 Redis 通信

RedisSocketConfiguration endpoint = new RedisSocketConfiguration("/var/run/redis");

LettuceConnectionFactory factory = new LettuceConnectionFactory(endpoint);

Redis 集群改进

此版本附带了对使用 Lettuce 驱动程序的 Redis 集群连接的连接处理的改进。以前的版本没有共享到 Redis 集群的基本 Lettuce 连接,这会导致性能下降,因为新连接始终建立新的集群连接。当发出多个命令时,此行为会导致影响,因为每个命令基本上都会使用新的 RedisConnection

默认情况下,现在为 Redis 集群连接启用了本机连接共享。其他使用模式(例如 Redis 独立模式)在以前的版本中已经使用了连接共享。以下示例显示了如何使用共享本机连接创建 LettuceConnectionFactory

RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(…);

LettuceConnectionFactory factory = new LettuceConnectionFactory(clusterConfiguration);
factory.setShareNativeConnection(true);

某些操作(例如阻塞操作)需要专用连接,以避免影响将在同一本机连接上运行的其他进程。如果您的应用程序严重依赖于阻塞 Redis 命令,您可以为 Redis 集群连接启用池以缓冲连接创建。启用池是客户端配置方面。启用池后,LettuceConnectionFactory 会将池应用于配置的 Redis 使用方案。您可以使用 LettucePoolingClientConfiguration 作为入口点来启用池,如下例所示

LettucePoolingClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().poolConfig(…).build();
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(…);

LettuceConnectionFactory factory = new LettuceConnectionFactory(clusterConfiguration, clientConfiguration);

通过引入 ReadFrom 设置和简化的集群连接处理,我们现在可以通过使用 SCAN 命令支持集群范围的键空间扫描。在幕后,驱动程序维护一个有状态的游标,允许您迭代保存集群中键的所有主/从节点。使用连接的 scan(…) 方法可以获得与在 Redis 独立模式设置中使用该方法相同的体验,如下例所示

Cursor<byte[]> scan = clusterConnection.keyCommands()
  .scan(ScanOptions.scanOptions().match("foo*").build());
scan.forEachRemaining(key -> …);

键空间扫描还为所有 Redis 操作模式提供反应式变体。在反应式 Redis 模板 API 上调用 scan(…) 会返回键的 Flux。生成的 Flux 具有背压感知能力,并在有足够需求扫描整个键空间时将需求转换为 SCAN 调用。如果满足需求,它将停止扫描。以下示例构建了这样的 Flux

Flux<String> scan = redisTemplate.scan(ScanOptions.scanOptions().match("something*").build());

Redis 存储库改进

此版本附带了对 Redis 存储库的按示例查询支持。按示例查询是一种用户友好的查询技术,具有简单的界面。它允许动态创建查询,并且不需要您编写包含字段名的查询。按示例查询的本质不需要查询语言,因为实际查询源自 Example 对象。您现在可以定义一个 Example 来查询存储在 Redis 哈希中的索引值。Redis 存储库可以实现 QueryByExampleExecutor 片段以继承按示例查询方法。请查看以下代码片段

interface PersonRepository extends CrudRepository<Person, String>, QueryByExampleExecutor<Person> {
}

PersonRepository repository = …;

Person eddard = new Person("eddard", "stark");
Person tyrion = new Person("tyrion", "lannister");
Person robb = new Person("robb", "stark");
Person jon = new Person("jon", "snow");
Person arya = new Person("arya", "stark");

repository.saveAll(Arrays.asList(eddard, tyrion, robb, jon, arya));

List<Person> result = repository.findAll(Example.of(new Person(null, "stark")));

此代码插入了一批 Person 对象。Example 对象定义了一个探测器,只设置了姓氏。查询引擎创建了一个查询,其中仅包含非空字段(默认情况下),查询 lastnamestark 的对象。

有关更多详细信息,请参阅 按示例查询示例

Redis 存储库现在支持类型别名,您可以通过使用 @TypeAlias 注释您的域类来使用它。默认情况下,Redis 中的类型提示使用完全限定的类名。您可以应用别名来自定义类型名称并减少 Redis 内存使用量。

以下示例持久化 Person 的实例

package com.acme;

@TypeAlias("person")
class Person {
  // …
}

此代码导致使用类型提示 (person) 而不是 com.acme.Person。用于将实体存储在 Redis 中的相应命令如下所示

HMSET "person:19315449-cda2-4f5c-b696-9cb8018fa1f9" "_class" "person" "id" "19315449-cda2-4f5c-b696-9cb8018fa1f9"

Redis 模块中还添加了一些其他增强功能,因此请务必查看参考文档中的 新功能 部分,以了解有关键空间扫描、反应式发布/订阅和新命令的更多信息。

获取 Spring 新闻通讯

与 Spring 新闻通讯保持联系

订阅

抢先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部