永続化エンティティ

org.springframework.data.cassandra パッケージにある CassandraTemplate クラス (およびそのリアクティブ版 ReactiveCassandraTemplate) は、Spring の Cassandra サポートの中心クラスであり、データベースと対話するための豊富な機能セットを提供します。このテンプレートは、Cassandra を作成、更新、削除、クエリするための便利な操作を提供し、ドメインオブジェクトと Cassandra テーブル内の行との間のマッピングを提供します。

構成が完了すると、テンプレートインスタンスはスレッドセーフになり、複数のインスタンス間で再利用できます。

Cassandra の行とアプリケーションドメインクラス間のマッピングは、CassandraConverter インターフェースの実装に委譲することによって行われます。Spring はデフォルトの実装である MappingCassandraConverter を提供しますが、独自のカスタムコンバーターを作成することもできます。詳細については、Cassandra 変換のセクションを参照してください。

CassandraTemplate クラスは CassandraOperations インターフェースを実装し、そのリアクティブバリアント ReactiveCassandraTemplate は ReactiveCassandraOperations を実装します。[Reactive]CassandraOperations のメソッドは、Cassandra にすでに精通している開発者にとって API が親しみやすいものとなるよう、可能な限り Cassandra で利用可能なメソッドにちなんで命名されています。

例: selectinsertdeleteupdate などのメソッドが見つかります。設計ゴールは、ベース Cassandra ドライバーと [Reactive]CassandraOperations の使用の間での移行をできるだけ簡単にすることでした。2 つの API の主な違いは、CassandraOperations には CQL オブジェクトやクエリオブジェクトの代わりにドメインオブジェクトを渡すことができることです。

[Reactive]CassandraTemplate インスタンスの操作を参照するための推奨される方法は、[Reactive]CassandraOperations インターフェースを使用することです。

[Reactive]CassandraTemplate で使用されるデフォルトのコンバーター実装は MappingCassandraConverter です。MappingCassandraConverter は追加のメタデータを使用してオブジェクトの行へのマッピングを指定できますが、フィールドとテーブル名のマッピングの規則を使用して、追加のメタデータを含まないオブジェクトを変換することもできます。これらの規則とマッピングアノテーションの使用については、「マッピング」の章で説明されています。

[Reactive]CassandraTemplate のもう 1 つの中心的な機能は、Cassandra Java ドライバーでスローされた例外を Spring の移植可能なデータアクセス例外階層に変換することです。詳細については、例外変換に関するセクションを参照してください。

テンプレート API にはさまざまな実行モデルのフレーバーがあります。基本的な CassandraTemplate は、ブロッキング (命令型同期) 実行モデルを使用します。非同期実行および ListenableFuture インスタンスとの同期には AsyncCassandraTemplate を使用でき、リアクティブ実行には ReactiveCassandraTemplate を使用できます。

CassandraTemplate のインスタンス化

CassandraTemplate は常に Spring Bean として構成する必要がありますが、直接インスタンス化できる例を前に示しました。ただし、Spring モジュールを作成するコンテキストを想定しているため、Spring コンテナーの存在を前提としています。

Spring ApplicationContext をロードする方法に応じて、CassandraTemplate を取得するには 2 つの方法があります。

オートワイヤー

次の例に示すように、[Reactive]CassandraOperations をプロジェクトにオートワイヤーできます。

  • 命令的

  • リアクティブ

@Autowired
private CassandraOperations cassandraOperations;
@Autowired
private ReactiveCassandraOperations reactiveCassandraOperations;

すべての Spring オートワイヤーと同様に、これは ApplicationContext 内に型 [Reactive]CassandraOperations の Bean が 1 つだけ存在することを前提としています。複数の [Reactive]CassandraTemplate Bean がある場合 (同じプロジェクト内で複数のキースペースを操作する場合がこれに該当します)、@Qualifier アノテーションを使用して、自動接続する Bean を指定できます。

  • 命令的

  • リアクティブ

@Autowired
@Qualifier("keyspaceOneTemplateBeanId")
private CassandraOperations cassandraOperations;
@Autowired
@Qualifier("keyspaceOneTemplateBeanId")
private ReactiveCassandraOperations reactiveCassandraOperations;

Bean ApplicationContext による検索

次の例に示すように、ApplicationContext から [Reactive]CassandraTemplate Bean を検索することもできます。

  • 命令的

  • リアクティブ

CassandraOperations cassandraOperations = applicationContext.getBean("cassandraTemplate", CassandraOperations.class);
ReactiveCassandraOperations cassandraOperations = applicationContext.getBean("ReactiveCassandraOperations", ReactiveCassandraOperations.class);

行のクエリ

Query および Criteria クラスを使用してクエリを表現できます。これらのクラスには、ltlteis などのネイティブ Cassandra 述語演算子名を反映するメソッド名が付いています。

Query クラスと Criteria クラスは流れるような API スタイルに従っているため、理解しやすいコードを使用しながら、複数のメソッド条件とクエリを簡単に チェーンで組み合わせることができます。静的インポートは、Java で Query および Criteria インスタンスを作成するときに読みやすさを向上させるために使用されます。

テーブル内の行のクエリ

前のセクションでは、[Reactive]CassandraTemplate で selectOneById メソッドを使用して単一のオブジェクトを取得する方法を説明しました。そうすると、単一のドメインオブジェクトが返されます。ドメインオブジェクトのリストとして返される行のコレクションをクエリすることもできます。名前と年齢の値がテーブルの行として格納された多数の Person オブジェクトがあり、各個人が口座残高を持っていると仮定すると、次のコードを使用してクエリを実行できます。

[Reactive]CassandraTemplate を使用した行のクエリ
  • 命令的

  • リアクティブ

import static org.springframework.data.cassandra.core.query.Criteria.where;
import static org.springframework.data.cassandra.core.query.Query.query;

…

List<Person> result = cassandraTemplate.select(query(where("age").is(50))
  .and(where("balance").gt(1000.00d)).withAllowFiltering(), Person.class);
import static org.springframework.data.cassandra.core.query.Criteria.where;
import static org.springframework.data.cassandra.core.query.Query.query;

…

Flux<Person> result = reactiveCassandraTemplate.select(query(where("age").is(50))
  .and(where("balance").gt(1000.00d)).withAllowFiltering(), Person.class);

selectselectOnestream メソッドは、Query オブジェクトをパラメーターとして受け取ります。このオブジェクトは、クエリの実行に使用される条件とオプションを定義します。この条件は、新しい Criteria オブジェクトをインスタンス化する where という名前の静的ファクトリメソッドを持つ Criteria オブジェクトを使用して指定されます。クエリを読みやすくするために、org.springframework.data.cassandra.core.query.Criteria.where および Query.query には静的インポートを使用することをお勧めします。

このクエリは、指定された条件を満たす Person オブジェクトのリストを返します。Criteria クラスには、Apache Cassandra で提供される演算子に対応する次のメソッドがあります。

Criteria クラスのメソッド

  • CriteriaDefinition gt (Object value)> 演算子を使用して基準を作成します。

  • CriteriaDefinition gte (Object value)>= 演算子を使用して基準を作成します。

  • CriteriaDefinition in (Object…​ values): varargs 引数に IN 演算子を使用して、基準を作成します。

  • CriteriaDefinition in (Collection<?> collection): コレクションを使用して IN 演算子を使用して、基準を作成します。

  • CriteriaDefinition is (Object value): フィールドマッチング (column = value) を使用して基準を作成します。

  • CriteriaDefinition lt (Object value)< 演算子を使用して基準を作成します。

  • CriteriaDefinition lte (Object value) 演算子を使用して基準を作成します。

  • CriteriaDefinition like (Object value)LIKE 演算子を使用して基準を作成します。

  • CriteriaDefinition contains (Object value)CONTAINS 演算子を使用して基準を作成します。

  • CriteriaDefinition containsKey (Object key)CONTAINS KEY 演算子を使用して基準を作成します。

Criteria は、一度作成されると不変になります。

Query クラスのメソッド

Query クラスには、クエリのオプションを提供するために使用できる追加のメソッドがいくつかあります。

  • Query by (CriteriaDefinition…​ criteria)Query オブジェクトの作成に使用されます。

  • Query and (CriteriaDefinition criteria): クエリに追加の条件を追加するために使用されます。

  • Query columns (Columns columns): クエリ結果に含める列を定義するために使用されます。

  • Query limit (Limit limit): 返される結果のサイズを指定された制限に制限するために使用されます (SELECT 制限を使用)。

  • Query limit (long limit): 返される結果のサイズを指定された制限に制限するために使用されます (SELECT 制限を使用)。

  • Query pageRequest (Pageable pageRequest)SortPagingStatefetchSize をクエリに関連付けるために使用されます (ページングに使用されます)。

  • Query pagingState (ByteBuffer pagingState)ByteBuffer をクエリに関連付けるために使用されます (ページングに使用されます)。

  • Query queryOptions (QueryOptions queryOptions)QueryOptions をクエリに関連付けるために使用されます。

  • Query sort (Sort sort): 結果の並べ替え定義を提供するために使用されます。

  • Query withAllowFiltering ()ALLOW FILTERING クエリをレンダリングするために使用されます。

Query は、一度作成されると不変になります。メソッドを呼び出すと、新しい不変 (中間) Query オブジェクトが作成されます。

行をクエリするためのメソッド

Query クラスには、行を返す次のメソッドがあります。

  • List<T> select (Query query, Class<T> entityClass): テーブルから型 T のオブジェクトのリストをクエリします。

  • T selectOne (Query query, Class<T> entityClass): テーブルから型 T の単一オブジェクトをクエリします。

  • Slice<T> slice (Query query, Class<T> entityClass): テーブルから型 T のオブジェクトの Slice をクエリすることによって、ページングを開始または継続します。

  • Stream<T> stream (Query query, Class<T> entityClass): テーブルから型 T のオブジェクトのストリームをクエリします。

  • List<T> select (String cql, Class<T> entityClass): CQL ステートメントを指定して、テーブルから T 型のオブジェクトのリストを取得するアドホッククエリ。

  • T selectOne (String cql, Class<T> entityClass): CQL ステートメントを指定して、テーブルから T 型の単一オブジェクトに対するアドホッククエリを実行します。

  • Stream<T> stream (String cql, Class<T> entityClass): CQL ステートメントを指定して、テーブルから型 T のオブジェクトのストリームに対するアドホッククエリを実行します。

クエリメソッドでは、返されるターゲット型 T を指定する必要があります。

Fluent テンプレート API

[Reactive]CassandraOperations インターフェースは、Apache Cassandra とのより低レベルのインタラクションに関して中心的なコンポーネントの 1 つです。幅広いメソッドを提供します。各メソッドには複数のオーバーロードがあります。そのほとんどは、API のオプション (null 可能) 部分をカバーします。

FluentCassandraOperations とそのリアクティブバリアント ReactiveFluentCassandraOperations は、[Reactive]CassandraOperations の一般的なメソッドに対してより狭いインターフェースを提供し、より読みやすく流れるような API を提供します。エントリポイント (query(…)insert(…)update(…)delete(…)) は、実行する操作に基づいた自然な命名スキームに従います。エントリポイントから先に進むと、API は、実際の [Reactive]CassandraOperations を呼び出す終了メソッドに開発者を導くコンテキスト依存のメソッドのみを提供するように設計されています。次の例は、流れるような API を示しています。

  • 命令的

  • リアクティブ

List<SWCharacter> all = ops.query(SWCharacter.class)
  .inTable("star_wars")                        (1)
  .all();
1SWCharacter が @Table でテーブル名を定義する場合、またはクラス名をテーブル名として使用しても問題ない場合は、この手順をスキップします。
Flux<SWCharacter> all = ops.query(SWCharacter.class)
  .inTable("star_wars")                        (1)
  .all();
1SWCharacter が @Table でテーブル名を定義する場合、またはクラス名をテーブル名として使用しても問題ない場合は、この手順をスキップします。

Cassandra のテーブルにさまざまな型のエンティティ ( SWCharacters のテーブル内の Jedi など) が保持されている場合、さまざまな型を使用してクエリ結果をマップできます。as(Class<?> targetType) を使用して結果を別のターゲット型にマップできますが、query(Class<?> entityType) は引き続きクエリとテーブル名に適用されます。次の例では、query メソッドと as メソッドを使用します。

  • 命令的

  • リアクティブ

List<Jedi> all = ops.query(SWCharacter.class)    (1)
  .as(Jedi.class)                                (2)
  .matching(query(where("jedi").is(true)))
  .all();
1 クエリフィールドは、SWCharacter 型に対してマップされます。
2 結果の行は Jedi にマップされます。
Flux<Jedi> all = ops.query(SWCharacter.class)    (1)
  .as(Jedi.class)                                (2)
  .matching(query(where("jedi").is(true)))
  .all();
1 クエリフィールドは、SWCharacter 型に対してマップされます。
2 結果の行は Jedi にマップされます。
interface 型から as(Class<?>) まで指定するだけで、結果のドキュメントに射影を直接適用できます。

終了メソッド (first()one()all()stream()) は、単一のエンティティの取得と、List または Stream および同様の操作としての複数のエンティティの取得の間の切り替えを処理します。

新しい流れるようなテンプレート API メソッド (つまり query(..)insert(..)update(..)delete(..)) は、スレッドセーフなサポートオブジェクトを効果的に使用して CQL ステートメントを作成します。ただし、設計はさまざまな CQL ステートメントコンポーネントの final フィールドと変更に基づく構築に基づいているため、若い世代の JVM ヒープオーバーヘッドが追加されるという追加コストがかかります。多数のオブジェクトを挿入または削除する可能性がある場合 (ループ内など) には注意が必要です。

行の保存、更新、削除

[Reactive]CassandraTemplate は、ドメインオブジェクトを保存、更新、削除し、それらのオブジェクトを Cassandra で管理されるテーブルにマップするための簡単なメソッドを提供します。

型マッピング

Apache Cassandra 用の Spring Data は、型のサポートを保証するために DataStax Java ドライバーの CodecRegistry に依存しています。型が追加または変更されても、Apache Cassandra 用の Spring Data モジュールは変更を必要とせずに機能し続けます。現在の型マッピングマトリックスについては、CQL データ型 (英語) および "データマッピングと型変換" を参照してください。

行を挿入および更新するメソッド

[Reactive]CassandraTemplate には、オブジェクトを保存および挿入するための便利な方法がいくつかあります。変換プロセスをよりきめ細かく制御するには、Spring Converter インスタンスを MappingCassandraConverter (たとえば、Converter<Row, Person>) に登録できます。

挿入操作と更新操作の違いは、INSERT 操作では null 値が挿入されないことです。

INSERT 操作を使用する簡単な例は、POJO を保存することです。この場合、テーブル名は (完全修飾クラス名ではなく) 単純なクラス名によって決まります。オブジェクトを格納するテーブルは、マッピングメタデータを使用してオーバーライドできます。

挿入または更新する場合は、id プロパティを設定する必要があります。Apache Cassandra には ID を生成する手段がありません。

次の例では、保存操作を使用してその内容を取得します。

[Reactive]CassandraTemplate を使用したオブジェクトの挿入と取得
  • 命令的

  • リアクティブ

import static org.springframework.data.cassandra.core.query.Criteria.where;
import static org.springframework.data.cassandra.core.query.Query.query;
…

Person bob = new Person("Bob", 33);
cassandraTemplate.insert(bob);

Person queriedBob = cassandraTemplate.selectOneById(query(where("age").is(33)), Person.class);
import static org.springframework.data.cassandra.core.query.Criteria.where;
import static org.springframework.data.cassandra.core.query.Query.query;
…

Person bob = new Person("Bob", 33);
cassandraTemplate.insert(bob);

Mono<Person> queriedBob = reactiveCassandraTemplate.selectOneById(query(where("age").is(33)), Person.class);

次の操作を使用して挿入および保存できます。

  • void insert (Object objectToSave): Apache Cassandra テーブルにオブジェクトを挿入します。

  • WriteResult insert (Object objectToSave, InsertOptions options): Apache Cassandra テーブルにオブジェクトを挿入し、InsertOptions を適用します。

次の更新操作を使用できます。

  • void update (Object objectToSave): Apache Cassandra テーブル内のオブジェクトを更新します。

  • WriteResult update (Object objectToSave, UpdateOptions options): Apache Cassandra テーブル内のオブジェクトを更新し、UpdateOptions を適用します。

次の例に示すように、昔ながらの方法を使用して独自の CQL ステートメントを作成することもできます。

  • 命令的

  • リアクティブ

String cql = "INSERT INTO person (age, name) VALUES (39, 'Bob')";

cassandraTemplate().getCqlOperations().execute(cql);
String cql = "INSERT INTO person (age, name) VALUES (39, 'Bob')";

Mono<Boolean> applied = reactiveCassandraTemplate.getReactiveCqlOperations().execute(cql);

InsertOptions および UpdateOptions を使用する場合は、TTL、整合性レベル、軽量トランザクションなどの追加オプションを構成することもできます。

行はどのテーブルに挿入されますか ?

テーブルの操作に使用されるテーブル名は 2 つの方法で管理できます。デフォルトのテーブル名は、小文字で始まるように変更された単純なクラス名です。com.example.Person クラスのインスタンスは person テーブルに格納されます。2 番目の方法は、@Table アノテーションでテーブル名を指定することです。

バッチ内の個々のオブジェクトの挿入、更新、削除

Cassandra プロトコルは、バッチを使用した 1 回の操作で行のコレクションを挿入することをサポートしています。

[Reactive]CassandraTemplate インターフェースの次のメソッドは、この機能をサポートしています。

  • batchOps: 新しい [Reactive]CassandraBatchOperations を作成してバッチに追加します。

[Reactive]CassandraBatchOperations

  • insert: 単一のオブジェクト、配列 (var-args)、またはオブジェクトの Iterable を挿入して受け取ります。

  • update: 単一のオブジェクト、配列 (var-args)、またはオブジェクトの Iterable を更新対象として受け取ります。

  • delete: 単一のオブジェクト、配列 (var-args)、または削除するオブジェクトの Iterable を受け取ります。

  • withTimestamp: TTL をバッチに適用します。

  • execute: バッチを実行します。

テーブル内の行を更新する

更新の場合、複数の行を更新することを選択できます。

次の例は、+ 割り当てを使用して残高に 1 回限りの $50.00 ボーナスを追加することによって、単一のアカウントオブジェクトを更新することを示しています。

[Reactive]CasandraTemplate を使用した行の更新
  • 命令的

  • リアクティブ

import static org.springframework.data.cassandra.core.query.Criteria.where;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;

…

boolean applied = cassandraTemplate.update(Query.query(where("id").is("foo")),
  Update.create().increment("balance", 50.00), Account.class);
import static org.springframework.data.cassandra.core.query.Criteria.where;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.cassandra.core.query.Update;

…

Mono<Boolean> wasApplied = reactiveCassandraTemplate.update(Query.query(where("id").is("foo")),
  Update.create().increment("balance", 50.00), Account.class);

前述の Query に加えて、Update オブジェクトを使用して更新定義を提供します。Update クラスには、Apache Cassandra で使用可能な更新割り当てに一致するメソッドがあります。

ほとんどのメソッドは Update オブジェクトを返し、コードスタイルの目的で流れるような API を提供します。

行の更新を実行するメソッド

update メソッドは次のように行を更新できます。

  • boolean update (Query query, Update update, Class<?> entityClass): Apache Cassandra テーブル内のオブジェクトの選択を更新します。

Update クラスのメソッド

Update クラスは、そのメソッドが連鎖することを目的としているため、少しの「構文糖」を加えて使用できます。また、静的メソッド public static Update update(String key, Object value) と静的インポートを使用して、新しい Update インスタンスの作成を開始することもできます。

Update クラスには次のメソッドがあります。

  • AddToBuilder addTo (String columnName) AddToBuilder エントリポイント:

    • prepend(Object value) の更新: + 更新割り当てを使用して、コレクション値を既存のコレクションの先頭に追加します。

    • prependAll(Object…​ values) の更新: + 更新割り当てを使用して、すべてのコレクション値を既存のコレクションの先頭に追加します。

    • append(Object value) の更新: + 更新割り当てを使用して、コレクション値を既存のコレクションに追加します。

    • append(Object…​ values) の更新: + 更新割り当てを使用して、すべてのコレクション値を既存のコレクションに追加します。

    • entry(Object key, Object value) の更新: + 更新割り当てを使用してマップエントリを追加します。

    • addAll(Map<? extends Object, ? extends Object> map) の更新: + 更新割り当てを使用して、すべてのマップエントリをマップに追加します。

  • Update remove (String columnName, Object value)- 更新割り当てを使用して、コレクションから値を削除します。

  • Update clear (String columnName): コレクションをクリアします。

  • Update increment (String columnName, Number delta)+ 更新割り当てを使用して更新します。

  • Update decrement (String columnName, Number delta)- 更新割り当てを使用して更新します。

  • Update set (String columnName, Object value)= 更新割り当てを使用して更新します。

  • SetBuilder  セット (String columnName) SetBuilder エントリポイント:

    • atIndex(int index).to(Object value) の更新: = 更新割り当てを使用して、指定されたインデックスのコレクションを値に設定します。

    • atKey(String object).to(Object value) の更新: 指定されたキーのマップエントリを = 更新割り当ての値に設定します。

次のリストは、いくつかの更新例を示しています。

// UPDATE … SET key = 'Spring Data';
Update.update("key", "Spring Data")

// UPDATE … SET key[5] = 'Spring Data';
Update.empty().set("key").atIndex(5).to("Spring Data");

// UPDATE … SET key = key + ['Spring', 'DATA'];
Update.empty().addTo("key").appendAll("Spring", "Data");

Update は一度作成されると不変であることに注意してください。メソッドを呼び出すと、新しい不変 (中間) Update オブジェクトが作成されます。

行を削除する方法

次のオーバーロードされたメソッドを使用して、データベースからオブジェクトを削除できます。

  • boolean delete (Query query, Class<?> entityClass)Query で選択したオブジェクトを削除します。

  • T delete (T entity): 指定されたオブジェクトを削除します。

  • T delete (T entity, QueryOptions queryOptions)QueryOptions を適用して指定されたオブジェクトを削除します。

  • boolean deleteById (Object id, Class<?> entityClass): 指定された ID を使用してオブジェクトを削除します。

楽観的ロック

@Version アノテーションは、Cassandra のコンテキストにおける JPA と同様の構文を提供し、バージョンが一致する行にのみ更新が適用されるようにします。オプティミスティックロックは、Cassandra の軽量トランザクションを利用して、条件付きで行を挿入、更新、削除します。INSERT ステートメントは IF NOT EXISTS 条件で実行されます。更新と削除の場合、バージョンプロパティの実際の値が UPDATE 条件に追加され、その間に別の操作で行が変更された場合でも変更は影響しません。その場合、OptimisticLockingFailureException がスローされます。次の例は、これらの機能を示しています。

@Table
class Person {

  @Id String id;
  String firstname;
  String lastname;
  @Version Long version;
}

Person daenerys = template.insert(new Person("Daenerys"));                            (1)

Person tmp = template.findOne(query(where("id").is(daenerys.getId())), Person.class); (2)

daenerys.setLastname("Targaryen");
template.save(daenerys);                                                              (3)

template.save(tmp); // throws OptimisticLockingFailureException                       (4)
1 最初にドキュメントを挿入します。version は 0 に設定されます。
2 挿入したばかりのドキュメントを読み込みます。version は 0 のままです。
3version = 0 でドキュメントを更新します。lastname を設定し、version を 1 にバンプします。
4version = 0 がまだある、以前にロードされたドキュメントを更新してみてください。現在の version は 1 であるため、操作は OptimisticLockingFailureException で失敗します。
オプティミスティックロックは単一エンティティ操作でのみサポートされ、バッチ操作ではサポートされません。