領先一步
VMware 提供培訓與認證,加速您的進程。
瞭解更多這篇部落格文章說明了 Spring Data Lovelace 中 Apache Cassandra 與 Redis 的新功能與值得注意之處。也請務必查看關於 Spring Data Lovelace 中 MongoDB 的新功能的部落格文章。
Spring Data Lovelace 上週才剛發布正式版本,現在是簡要介紹我們新增功能的時候了。這次的版本包含了非常多的功能。
在這篇部落格文章中,我將介紹 Apache Cassandra 與 Redis。
在這個版本中,我們使用 Cassandra 專用的類型來改進資料存取,引入了對生命週期事件的支援,改善了 Java 和 Kotlin 的程式設計體驗,並且包含了各種其他的改進。讓我們來看看這個版本如何協助您改善對 Cassandra 的資料存取。
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
)。為此,我們在 String
與 Currency
之間提供轉換器,並透過 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
}
Tuple 在先前版本的 Spring Data for Apache Cassandra 中並不是真的可用。使用 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 類別。您不再需要直接與 TupleType
和 TupleValue
互動 (儘管您仍然可以),但您可以使用類型安全的方法來表示 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
select
和 selectOne
方法中,於從資料庫中擷取列之後呼叫。
onAfterConvert
:在 CassandraTemplate
select
和 selectOne
方法中,於從資料庫擷取的列轉換為 POJO之後呼叫。
生命週期事件只會針對根層級類型發出。用作實體根目錄中屬性的複雜類型不受事件發布的影響。
請參閱我們的 生命週期事件範例。
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 使用範例,以取得更多詳細資訊。
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
類型的欄位名稱來對應。您可以使用終止方法:first()
、one()
、all()
或 stream()
,在檢索單一實體和檢索多個物件作為 List
或 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 隨附了各種主題的改進,這些改進未包含在 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
讓您可以在發出唯讀命令 (例如 GET
或 SMEMBERS
) 時選擇特定的節點類型。您可以使用 Lettuce 的其中一種預定義設定,或建立新的 ReadFrom
策略。在所有提供複本的設定中都會考慮 ReadFrom
:Redis Sentinel、Redis 叢集和靜態 Master/Replica 設定 (例如 AWS ElastiCache),這將我們帶到下一個改進。
您可以使用 AWS ElastiCache 或任何其他靜態 Master/Replica 設定 (也就是說,使用具有一個或多個專用複本的 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);
此版本隨附了使用 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 儲存庫的範例查詢支援。範例查詢是一種使用者友善的查詢技術,具有簡單的介面。它允許動態查詢建立,並且不需要您編寫包含欄位名稱的查詢。範例查詢的本質不需要查詢語言,因為實際查詢來自 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
物件定義了一個探針,其中只設定了姓氏。查詢引擎會建立一個僅包含非空欄位 (依預設) 的查詢,以查詢 lastname
為 stark
的物件。
請參閱 範例查詢範例 以取得更多詳細資訊。
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 模組中,因此請務必查看參考文件中 新功能 區段,以了解有關金鑰空間掃描、反應式 Pub/Sub 和新命令的更多資訊。