このバージョンはまだ開発中であり、まだ安定しているとは見なされていません。最新の安定バージョンについては、Spring Data Cassandra 4.4.5 を使用してください! |
マッピング
豊富なオブジェクトマッピングのサポートは、MappingCassandraConverter
によって提供されます。MappingCassandraConverter
には、ドメインオブジェクトを CQL テーブルにマップする機能の完全な機能セットを提供する豊富なメタデータモデルがあります。
マッピングメタデータモデルは、ドメインオブジェクトのアノテーションを使用して設定されます。ただし、インフラストラクチャは、メタデータの唯一のソースとしてアノテーションを使用することに限定されません。MappingCassandraConverter
では、一連の規則に従って、追加のメタデータを提供せずにドメインオブジェクトをテーブルにマップすることもできます。
この章では、MappingCassandraConverter
の機能、ドメインオブジェクトをテーブルにマッピングするための規則の使用メソッド、およびアノテーションベースのマッピングメタデータでそれらの規則をオーバーライドする方法について説明します。
オブジェクトマッピングの基礎
このセクションでは、Spring Data オブジェクトマッピング、オブジェクト作成、フィールドとプロパティへのアクセス、可変性と不変性の基礎について説明します。このセクションは、基になるデータストア(JPA など)のオブジェクトマッピングを使用しない Spring Data モジュールにのみ適用されることに注意してください。また、インデックス、列名やフィールド名のカスタマイズなど、ストア固有のオブジェクトマッピングについては、ストア固有のセクションを参照してください。
Spring Data オブジェクトマッピングの中心的なロールは、ドメインオブジェクトのインスタンスを作成し、ストアネイティブデータ構造をそれらにマッピングすることです。つまり、2 つの基本的な手順が必要です。
公開されたコンストラクターの 1 つを使用したインスタンスの作成。
すべての公開されたプロパティを具体化するインスタンスの設定。
オブジェクト作成
Spring Data は、その型のオブジェクトの具体化に使用される永続エンティティのコンストラクターを自動的に検出しようとします。解決アルゴリズムは次のように機能します。
@PersistenceCreator
でアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。コンストラクターが 1 つしかない場合は、それが使用されます。
複数のコンストラクターがあり、そのうちの 1 つだけに
@PersistenceCreator
アノテーションが付けられている場合は、それが使用されます。型が Java
Record
の場合、標準コンストラクターが使用されます。引数のないコンストラクターがある場合は、それが使用されます。他のコンストラクターは無視されます。
値の解決では、コンストラクター / ファクトリメソッドの引数名がエンティティのプロパティ名と一致することを前提としています。つまり、マッピングのすべてのカスタマイズ(異なるデータストア列またはフィールド名など)を含め、プロパティが入力されたかのように解決が実行されます。これには、クラスファイルで利用可能なパラメーター名情報、またはコンストラクターに存在する @ConstructorProperties
アノテーションも必要です。
値の解決は、ストア固有の SpEL 式を使用した Spring Framework の @Value
値アノテーションを使用してカスタマイズできます。詳細については、ストア固有のマッピングに関するセクションを参照してください。
プロパティ設定
エンティティのインスタンスが作成されると、Spring Data はそのクラスの残りのすべての永続プロパティを設定します。エンティティのコンストラクターによってすでに入力されていない場合(つまり、コンストラクターの引数リストを介して使用される場合)、ID プロパティが最初に入力され、循環オブジェクト参照の解決が可能になります。その後、コンストラクターによってまだ設定されていないすべての非一時的なプロパティがエンティティインスタンスに設定されます。そのために、次のアルゴリズムを使用します。
プロパティが不変であるが
with …
メソッドを公開している場合(以下を参照)、with …
メソッドを使用して、新しいプロパティ値を持つ新しいエンティティインスタンスを作成します。プロパティアクセス(つまり、getter および setter を介したアクセス)が定義されている場合、setter メソッドを呼び出しています。
プロパティが変更可能な場合、フィールドを直接設定します。
プロパティが不変の場合、永続化操作(オブジェクト作成を参照)で使用されるコンストラクターを使用して、インスタンスのコピーを作成します。
デフォルトでは、フィールド値を直接設定します。
次のエンティティを見てみましょう。
class Person {
private final @Id Long id; (1)
private final String firstname, lastname; (2)
private final LocalDate birthday;
private final int age; (3)
private String comment; (4)
private @AccessType(Type.PROPERTY) String remarks; (5)
static Person of(String firstname, String lastname, LocalDate birthday) { (6)
return new Person(null, firstname, lastname, birthday,
Period.between(birthday, LocalDate.now()).getYears());
}
Person(Long id, String firstname, String lastname, LocalDate birthday, int age) { (6)
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.age = age;
}
Person withId(Long id) { (1)
return new Person(id, this.firstname, this.lastname, this.birthday, this.age);
}
void setRemarks(String remarks) { (5)
this.remarks = remarks;
}
}
1 | identifier プロパティは final ですが、コンストラクターで null に設定されます。クラスは、識別子の設定に使用される withId(…) メソッドを公開します。インスタンスがデータストアに挿入され、識別子が生成されたとき。元の Person インスタンスは、新しいインスタンスが作成されるときに変更されません。通常、ストア管理される他のプロパティにも同じパターンが適用されますが、永続化操作のために変更する必要がある場合があります。永続化コンストラクター(6 を参照)は事実上コピーコンストラクターであり、プロパティの設定は新しい識別子値が適用された新しいインスタンスの作成に変換されるため、wither メソッドはオプションです。 |
2 | firstname および lastname プロパティは、getter を介して潜在的に公開される通常の不変のプロパティです。 |
3 | age プロパティは不変ですが、birthday プロパティから派生しています。示されている設計では、Spring Data は宣言された唯一のコンストラクターを使用するため、データベース値はデフォルト設定よりも優先されます。計算が優先されることを意図している場合でも、このコンストラクターがパラメーターとして age を受け取ることが重要です(無視される可能性があります)。そうしないと、プロパティ生成ステップは age フィールドを設定しようとし、不変で no with … メソッドが存在します。 |
4 | comment プロパティは変更可能で、そのフィールドを直接設定することによって入力されます。 |
5 | remarks プロパティは変更可能で、setter メソッドを呼び出すことによって設定されます。 |
6 | このクラスは、オブジェクト作成用のファクトリメソッドとコンストラクターを公開します。ここでの中心的な考え方は、@PersistenceCreator によるコンストラクターの曖昧性解消の必要性を回避するために、追加のコンストラクターの代わりにファクトリメソッドを使用することです。代わりに、プロパティのデフォルト設定はファクトリメソッド内で処理されます。Spring Data でオブジェクトのインスタンス化にファクトリメソッドを使用する場合は、@PersistenceCreator でアノテーションを付けます。 |
一般的な推奨事項
不変オブジェクトにこだわる — 不変オブジェクトは、オブジェクトを具体化するのはコンストラクターのみを呼び出すだけなので、簡単に作成できます。また、これにより、クライアントオブジェクトがオブジェクトの状態を操作できるようにする setter メソッドがドメインオブジェクトに散らばるのを防ぎます。それらが必要な場合は、同じ場所に配置された限られた型でのみ呼び出せるように、パッケージを保護することをお勧めします。コンストラクターのみの実体化は、プロパティの設定よりも最大 30% 高速です。
all-args コンストラクターを提供する — エンティティを不変の値としてモデル化できない、またはしたくない場合でも、オブジェクトのマッピングがプロパティの設定をスキップできるため、エンティティのすべてのプロパティを引数として取るコンストラクターを提供することには価値があります。最適なパフォーマンスのため。
@PersistenceCreator
を回避するために、オーバーロードされたコンストラクターの代わりにファクトリメソッドを使用します — 最適なパフォーマンスに必要なすべての引数コンストラクターでは、通常、自動生成識別子などを省略したアプリケーションユースケース固有のコンストラクターを公開します。これらの all-args コンストラクターのバリアントを公開する静的ファクトリメソッド。生成されたインスタンス生成クラスとプロパティアクセッサクラスを使用できるようにする制約を必ず守ってください。
生成される識別子については、すべての引数の永続化コンストラクター(推奨)または
with …
メソッドと組み合わせて final フィールドを使用しますLombok を使用してボイラープレートコードを回避します — 永続化操作は通常、すべての引数を取るコンストラクターを必要とするため、その宣言はフィールド割り当てに対するボイラープレートパラメーターの退屈な繰り返しとなりますが、Lombok の
@AllArgsConstructor
を使用することで回避することができます。
プロパティのオーバーライド
Java では、サブクラスがスーパークラスで同じ名前ですでに宣言されているプロパティを定義できる、ドメインクラスの柔軟な設計が可能です。次の例を検討してください。
public class SuperType {
private CharSequence field;
public SuperType(CharSequence field) {
this.field = field;
}
public CharSequence getField() {
return this.field;
}
public void setField(CharSequence field) {
this.field = field;
}
}
public class SubType extends SuperType {
private String field;
public SubType(String field) {
super(field);
this.field = field;
}
@Override
public String getField() {
return this.field;
}
public void setField(String field) {
this.field = field;
// optional
super.setField(field);
}
}
どちらのクラスも、割り当て可能な型を使用して field
を定義します。ただし、SubType
は SuperType.field
をシャドウします。クラスの設計によっては、コンストラクターを使用することが SuperType.field
を設定するための唯一のデフォルトのアプローチである可能性があります。または、setter で super.setField(…)
を呼び出すと、SuperType
で field
を設定できます。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、これらすべてのメカニズムはある程度の競合を引き起こします。Spring Data は、型が割り当て可能でない場合、スーパー型のプロパティをスキップします。つまり、オーバーライドされたプロパティの型は、オーバーライドとして登録されるスーパー型のプロパティ型に割り当て可能である必要があります。そうでない場合、スーパー型のプロパティは一時的なものと見なされます。通常、個別のプロパティ名を使用することをお勧めします。
Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。
どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに
@Transient
アノテーションを付けることで、プロパティを除外できます。データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。
@AccessType(PROPERTY)
を使用することは、通常、setter 実装のさらなる仮定を行わずにスーパープロパティを設定することができないため、使用できません。
Kotlin サポート
Spring Data は、Kotlin の仕様を適合させて、オブジェクトの作成と変更を可能にします。
Kotlin オブジェクトの作成
Kotlin クラスはインスタンス化がサポートされています。すべてのクラスはデフォルトで不変であり、変更可能なプロパティを定義するには明示的なプロパティ宣言が必要です。
Spring Data は、その型のオブジェクトの具体化に使用される永続エンティティのコンストラクターを自動的に検出しようとします。解決アルゴリズムは次のように機能します。
@PersistenceCreator
でアノテーションが付けられたコンストラクターがある場合は、それが使用されます。型が Kotlin データクラスの場合、プライマリコンストラクターが使用されます。
@PersistenceCreator
でアノテーションが付けられた単一の静的ファクトリメソッドがある場合は、それが使用されます。コンストラクターが 1 つしかない場合は、それが使用されます。
複数のコンストラクターがあり、そのうちの 1 つだけに
@PersistenceCreator
アノテーションが付けられている場合は、それが使用されます。型が Java
Record
の場合、標準コンストラクターが使用されます。引数のないコンストラクターがある場合は、それが使用されます。他のコンストラクターは無視されます。
次の data
クラス Person
を検討してください。
data class Person(val id: String, val name: String)
上記のクラスは、明示的なコンストラクターを持つ典型的なクラスにコンパイルされます。別のコンストラクターを追加してこのクラスをカスタマイズし、@PersistenceCreator
でアノテーションを付けてコンストラクターの設定を示します。
data class Person(var id: String, val name: String) {
@PersistenceCreator
constructor(id: String) : this(id, "unknown")
}
Kotlin は、パラメーターが提供されない場合にデフォルト値を使用できるようにすることで、パラメーターのオプションをサポートしています。Spring Data がパラメーターのデフォルト設定を持つコンストラクターを検出した場合、データストアが値を提供しない(または単に null
を返す)場合、Kotlin はパラメーターのデフォルト設定を適用できるため、これらのパラメーターは存在しません。name
のパラメーターのデフォルト設定を適用する次のクラスを検討してください。
data class Person(var id: String, val name: String = "unknown")
name
パラメーターが結果の一部ではないか、その値が null
であるたびに、name
は unknown
にデフォルト設定されます。
委譲プロパティは Spring Data ではサポートされていません。マッピングメタデータは、Kotlin データクラスの委譲プロパティをフィルタリングします。それ以外の場合は、プロパティに @Transient (Javadoc) アノテーションを付与することで、委譲プロパティの合成フィールドを除外できます。 |
Kotlin データクラスのプロパティ設定
Kotlin では、すべてのクラスはデフォルトで不変であり、可変プロパティを定義するには明示的なプロパティ宣言が必要です。次の data
クラス Person
を検討してください。
data class Person(val id: String, val name: String)
このクラスは事実上不変です。Kotlin が既存のオブジェクトからすべてのプロパティ値をコピーしてメソッドに引数として提供されたプロパティ値を適用する新しいオブジェクトインスタンスを作成する copy(…)
メソッドを生成するときに、新しいインスタンスを作成できます。
Kotlin オーバーライドプロパティ
Kotlin では、プロパティのオーバーライド (英語) を宣言して、サブクラスのプロパティを変更できます。
open class SuperType(open var field: Int)
class SubType(override var field: Int = 1) :
SuperType(field) {
}
このような配置では、field
という名前の 2 つのプロパティがレンダリングされます。Kotlin は、各クラスの各プロパティのプロパティアクセサー(getter および setter)を生成します。事実上、コードは次のようになります。
public class SuperType {
private int field;
public SuperType(int field) {
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
public final class SubType extends SuperType {
private int field;
public SubType(int field) {
super(field);
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
SubType
の Getter および setter は、SubType.field
のみを設定し、SuperType.field
は設定しません。このような配置では、コンストラクターを使用することが SuperType.field
を設定するための唯一のデフォルトのアプローチです。SubType
にメソッドを追加して this.SuperType.field = …
を介して SuperType.field
を設定することは可能ですが、サポートされている規則の範囲外です。プロパティは同じ名前を共有しますが、2 つの異なる値を表す可能性があるため、プロパティのオーバーライドによってある程度の競合が発生します。通常、個別のプロパティ名を使用することをお勧めします。
Spring Data モジュールは通常、異なる値を保持するオーバーライドされたプロパティをサポートします。プログラミングモデルの観点から、考慮すべきことがいくつかあります。
どのプロパティを永続化する必要がありますか(デフォルトでは、宣言されたすべてのプロパティになります)? これらに
@Transient
アノテーションを付けることで、プロパティを除外できます。データストアのプロパティを表す方法は? 異なる値に同じフィールド / 列名を使用すると、通常、データが破損するため、明示的なフィールド / 列名を使用してプロパティの少なくとも 1 つにアノテーションを付ける必要があります。
@AccessType(PROPERTY)
を使用すると、スーパープロパティが設定できないため使用できません。
Kotlin 値クラス
Kotlin 値クラスは、基礎となる概念を明示するために、より表現力豊かなドメインモデル用に設計されています。Spring Data は、値クラスを使用してプロパティを定義する型の読み取りと書き込みができます。
次のドメインモデルを検討してください。
@JvmInline
value class EmailAddress(val theAddress: String) (1)
data class Contact(val id: String, val name:String, val emailAddress: EmailAddress) (2)
1 | Null 非許容値型を持つ単純な値クラス。 |
2 | EmailAddress 値クラスを使用してプロパティを定義するデータクラス。 |
非プリミティブ値型を使用する null 非許容プロパティは、コンパイルされたクラスで値型にフラット化されます。Null 許容プリミティブ値型または Null 許容値内値型は、ラッパー型で表現され、データベース内での値型の表現方法に影響します。 |
データマッピングと型変換
このセクションでは、型が Apache Cassandra 表現にマッピングされる方法と、Apache Cassandra 表現からマッピングされる方法について説明します。
Apache Cassandra の Spring Data は、Apache Cassandra によって提供されるいくつかの型をサポートしています。これらの型に加えて、Apache Cassandra の Spring Data は、追加の型をマップするための組み込みコンバーターのセットを提供します。型変換を調整するために、独自のカスタムコンバーターを提供することもできます。詳細については、"カスタムコンバーターによるデフォルトマッピングのオーバーライド" を参照してください。次の表は、Spring Data 型を Cassandra 型にマップします。
タイプ | Cassandra 型 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
user type |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
サポートされている各型は、デフォルトの Cassandra データ型 (英語) にマップされます。次の例に示すように、Java 型は @CassandraType
を使用して他の Cassandra 型にマップできます。
@Table
public class EnumToOrdinalMapping {
@PrimaryKey String id;
@CassandraType(type = Name.INT) Condition asOrdinal;
}
public enum Condition {
NEW, USED
}
規約ベースのマッピング
MappingCassandraConverter
は、追加のマッピングメタデータが提供されない場合、ドメインオブジェクトを CQL テーブルにマッピングするためにいくつかの規則を使用します。規約は次のとおりです。
単純な (短い) Java クラス名は、小文字に変更されてテーブル名にマップされます。例:
com.bigbank.SavingsAccount
はsavingsaccount
という名前のテーブルにマップされます。コンバーターは、登録されている Spring
Converter
インスタンスを使用して、オブジェクトプロパティのテーブル列へのデフォルトのマッピングをオーバーライドします。オブジェクトのプロパティは、テーブル内の列との間の変換に使用されます。
CassandraMappingContext
上で NamingStrategy
を構成することで、規則を調整できます。命名戦略オブジェクトは、テーブル、列、またはユーザー定義型がエンティティクラスおよび実際のプロパティから派生する規則を実装します。
次の例は、NamingStrategy
を構成する方法を示しています。
CassandraMappingContext
での NamingStrategy
の構成 CassandraMappingContext context = new CassandraMappingContext();
// default naming strategy
context.setNamingStrategy(NamingStrategy.INSTANCE);
// snake_case converted to upper case (SNAKE_CASE)
context.setNamingStrategy(NamingStrategy.SNAKE_CASE.transform(String::toUpperCase));
マッピング設定
明示的に構成されていない限り、CassandraTemplate
の作成時に、デフォルトで MappingCassandraConverter
のインスタンスが作成されます。MappingCassandraConverter
の独自のインスタンスを作成して、起動時にドメインクラスのクラスパスをスキャンしてメタデータを抽出し、インデックスを構築する場所を指定できます。
また、独自のインスタンスを作成することにより、データベースとの間で特定のクラスをマッピングするために使用する Spring Converter
インスタンスを登録できます。次の構成クラスの例では、Cassandra マッピングサポートをセットアップします。
@Configuration
public class SchemaConfiguration extends AbstractCassandraConfiguration {
@Override
protected String getKeyspaceName() {
return "bigbank";
}
// the following are optional
@Override
public CassandraCustomConversions customConversions() {
return CassandraCustomConversions.create(config -> {
config.registerConverter(new PersonReadConverter()));
config.registerConverter(new PersonWriteConverter()));
});
}
@Override
public SchemaAction getSchemaAction() {
return SchemaAction.RECREATE;
}
// other methods omitted...
}
AbstractCassandraConfiguration
では、キースペースを定義するメソッドを実装する必要があります。AbstractCassandraConfiguration
には getEntityBasePackages(…)
というメソッドもあります。これをオーバーライドして、@Table
アノテーションが付けられたクラスをスキャンする場所をコンバーターに指示できます。
customConversions
メソッドをオーバーライドすることで、MappingCassandraConverter
にコンバーターを追加できます。
AbstractCassandraConfiguration は CassandraTemplate インスタンスを作成し、cassandraTemplate という名前でコンテナーに登録します。 |
メタデータベースのマッピング
Apache Cassandra サポート用の Spring Data 内のオブジェクトマッピング機能を最大限に活用するには、マップされたドメインオブジェクトに @Table
アノテーションを付ける必要があります。これにより、クラスパススキャナーがドメインオブジェクトを見つけて前処理し、必要なメタデータを抽出できるようになります。アノテーションが付けられたエンティティのみがスキーマアクションの実行に使用されます。最悪の場合、SchemaAction.RECREATE_DROP_UNUSED
操作によってテーブルが削除され、データが失われます。テーブルはセッションキースペースからアクセスされることに注意してください。ただし、カスタムキースペースを指定して、特定のキースペースのテーブル /UDT を使用することもできます。
次の例は、単純なドメインオブジェクトを示しています。
package com.mycompany.domain;
@Table
public class Person {
@Id
private String id;
@CassandraType(type = Name.VARINT)
private Integer ssn;
private String firstName;
private String lastName;
}
@Id アノテーションは、Cassandra 主キーに使用するプロパティをマッパーに伝えます。複合主キーには、わずかに異なるデータモデルが必要になる場合があります。 |
主キーの操作
Cassandra では、CQL テーブルに少なくとも 1 つのパーティションキーフィールドが必要です。テーブルでは、1 つ以上のクラスタリングキーフィールドを追加で宣言できます。CQL テーブルに複合主キーがある場合は、@PrimaryKeyClass
を作成して複合主キーの構造を定義する必要があります。この文脈では、「複合主キー」とは、1 つまたは複数のクラスタリング列とオプションで組み合わせられる 1 つまたは複数のパーティション列を意味します。
主キーでは、任意の単数の単純な Cassandra 型またはマップされたユーザー定義型を使用できます。コレクション型の主キーはサポートされていません。
単純な主キー
単純な主キーは、エンティティクラス内の 1 つのパーティションキーフィールドで構成されます。これは 1 つのフィールドのみであるため、パーティションキーであると想定して問題ありません。次のリストは、user_id
の主キーを持つ Cassandra で定義された CQL テーブルを示しています。
CREATE TABLE user (
user_id text,
firstname text,
lastname text,
PRIMARY KEY (user_id))
;
次の例は、前のリストで定義された Cassandra に対応するようにアノテーションが付けられた Java クラスを示しています。
@Table(value = "login_event")
public class LoginEvent {
@PrimaryKey("user_id")
private String userId;
private String firstname;
private String lastname;
// getters and setters omitted
}
複合キー
複合主キー (または複合キー) は、複数の主キーフィールドで構成されます。ただし、複合主キーは、複数のパーティションキー、パーティションキーとクラスタリングキー、または多数の主キーフィールドで構成される場合があります。
複合キーは、Spring Data または Apache Cassandra の 2 つの方法で表現できます。
エンティティに埋め込まれています。
@PrimaryKeyClass
を使用します。
複合キーの最も単純な形式は、1 つのパーティションキーと 1 つのクラスタリングキーを持つキーです。
次の例は、テーブルとその複合キーを表す CQL ステートメントを示しています。
CREATE TABLE login_event(
person_id text,
event_code int,
event_time timestamp,
ip_address text,
PRIMARY KEY (person_id, event_code, event_time))
WITH CLUSTERING ORDER BY (event_time DESC)
;
フラット複合主キー
フラット複合主キーは、フラットフィールドとしてエンティティ内に埋め込まれます。主キーフィールドには @PrimaryKeyColumn
というアノテーションが付けられます。選択には、クエリに個々のフィールドの述語を含めるか、MapId
を使用する必要があります。次の例は、フラットな複合主キーを持つクラスを示しています。
@Table(value = "login_event")
class LoginEvent {
@PrimaryKeyColumn(name = "person_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String personId;
@PrimaryKeyColumn(name = "event_code", ordinal = 1, type = PrimaryKeyType.PARTITIONED)
private int eventCode;
@PrimaryKeyColumn(name = "event_time", ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING)
private LocalDateTime eventTime;
@Column("ip_address")
private String ipAddress;
// getters and setters omitted
}
主キークラス
主キークラスは、エンティティの複数のフィールドまたはプロパティにマップされる複合主キークラスです。これには @PrimaryKeyClass
のアノテーションが付けられており、equals
メソッドと hashCode
メソッドを定義する必要があります。これらのメソッドの値の等価性のセマンティクスは、キーがマップされるデータベース型のデータベースの等価性と一致している必要があります。主キークラスは、リポジトリ (Id
型として) とともに使用し、単一の複雑なオブジェクトでエンティティの ID を表すことができます。次の例は、複合主キークラスを示しています。
@PrimaryKeyClass
class LoginEventKey implements Serializable {
@PrimaryKeyColumn(name = "person_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String personId;
@PrimaryKeyColumn(name = "event_code", ordinal = 1, type = PrimaryKeyType.PARTITIONED)
private int eventCode;
@PrimaryKeyColumn(name = "event_time", ordinal = 2, type = PrimaryKeyType.CLUSTERED, ordering = Ordering.DESCENDING)
private LocalDateTime eventTime;
// other methods omitted
}
次の例は、複合主キーの使用方法を示しています。
@Table(value = "login_event")
public class LoginEvent {
@PrimaryKey
private LoginEventKey key;
@Column("ip_address")
private String ipAddress;
// getters and setters omitted
}
埋め込みエンティティのサポート
埋め込みエンティティは、プロパティがテーブルにフラット化される Java ドメインモデル内の値オブジェクトを設計するために使用されます。次の例では、User.name
に @Embedded
というアノテーションが付けられていることがわかります。この結果、UserName
のすべてのプロパティが 3 つの列 (user_id
、firstname
、lastname
) で構成される user
テーブルに組み込まれます。
埋め込みエンティティには、単純なプロパティ型のみを含めることができます。埋め込まれたエンティティを別の埋め込まれたエンティティにネストすることはできません。 |
ただし、結果セット内の firstname
および lastname
列の値が実際には null
である場合、すべてのネストされたプロパティが null
である場合、@Embedded
の onEmpty
に従ってプロパティ name
全体が null
に設定されます。この null
のオブジェクトは null
です。
この動作とは逆に、USE_EMPTY
は、デフォルトのコンストラクター、または結果セットから null 許容パラメーター値を受け入れるコンストラクターを使用して、新しいインスタンスを作成しようとします。
public class User {
@PrimaryKey("user_id")
private String userId;
@Embedded(onEmpty = USE_NULL) (1)
UserName name;
}
public class UserName {
private String firstname;
private String lastname;
}
1 | firstname および lastname が null の場合、プロパティは null です。onEmpty=USE_EMPTY を使用して、そのプロパティの潜在的な null 値を使用して UserName をインスタンス化します。 |
@Embedded
アノテーションのオプションの prefix
要素を使用すると、値オブジェクトをエンティティに複数回埋め込むことができます。この要素は接頭辞を表し、埋め込みオブジェクト内の各列名の先頭に付加されます。複数のプロパティが同じ列名にレンダリングされる場合、プロパティは互いに上書きされることに注意してください。
|
マッピングアノテーションの概要
MappingCassandraConverter
はメタデータを使用して、Cassandra テーブル内の行へのオブジェクトのマッピングを実行できます。アノテーションの概要は次のとおりです。
@Id
: フィールドまたはプロパティレベルで適用され、ID 目的で使用されるプロパティをマークします。@Table
: クラスレベルで適用され、このクラスがデータベースへのマッピングの候補であることを示します。オブジェクトが格納されているテーブルの名前を指定できます。キースペースを指定すると、すべての DML および DDL 操作でテーブル名の先頭にキースペースが付けられます。@PrimaryKey
:@Id
に似ていますが、列名を指定できます。@PrimaryKeyColumn
: 主キー列の Cassandra 固有のアノテーション。クラスター化またはパーティション化などの主キー列属性を指定できます。単一または複数の属性で使用して、単一または複合 (複合) 主キーを示すことができます。エンティティ内のプロパティで使用する場合は、必ず@Id
アノテーションも適用してください。@PrimaryKeyClass
: クラスレベルで適用され、このクラスが複合主キークラスであることを示します。エンティティクラスの@PrimaryKey
で参照する必要があります。@Transient
: デフォルトでは、すべてのプライベートフィールドが行にマップされます。このアノテーションは、それが適用されるフィールドをデータベースへの保存から除外します。コンバーターはコンストラクター引数の値を具体化できないため、一時プロパティは永続コンストラクター内で使用できません。@PersistenceConstructor
: 指定されたコンストラクターをマークします — パッケージでさえ保護されたもの — データベースからオブジェクトをインスタンス化するときに使用します。コンストラクターの引数は、取得した行のキー値に名前でマップされます。@Value
: このアノテーションは Spring Framework の一部です。マッピングフレームワーク内では、コンストラクターの引数に適用できます。これにより、Spring 式言語ステートメントを使用して、データベースで取得したキーの値を、ドメインオブジェクトの構築に使用する前に変換できます。特定のRow
/UdtValue
/TupleValue
のプロパティを参照するには、次のような式を使用する必要があります。:@Value("#root.getString(0)")
ここで、root
は指定されたドキュメントのルートを指します。@ReadOnlyProperty
: フィールドレベルで適用して、プロパティを読み取り専用としてマークします。エンティティにバインドされた挿入および更新ステートメントには、このプロパティは含まれません。@Column
: フィールドレベルで適用されます。Cassandra テーブルで表される列名を記述します。名前はクラスのフィールド名とは異なります。コンストラクターの引数で使用して、コンストラクターの作成中に列名をカスタマイズできます。@Embedded
: フィールドレベルで適用されます。テーブルまたはユーザー定義型にマップされた型の埋め込みオブジェクトの使用を有効にします。埋め込みオブジェクトのプロパティは、その親の構造にフラット化されます。@Indexed
: フィールドレベルで適用されます。セッション初期化時に作成されるインデックスを記述します。@SASI
: フィールドレベルで適用されます。セッションの初期化中に SASI インデックスを作成できるようにします。@SaiIndexed
: フィールドレベルで適用されます。セッションの初期化中に SAI (ストレージ接続インデックス) インデックスの作成を定義できます。@CassandraType
: Cassandra データ型を指定するためにフィールドレベルで適用されます。デフォルトでは、型はプロパティ宣言から派生します。@VectorType
: Cassandra ベクトル型を指定するためにフィールドレベルで適用されます。このアノテーションは、スキーマ生成を使用する場合に必要です。@Frozen
: フィールドレベルでクラス型とパラメーター化された型に適用されます。List<@Frozen UserDefinedPersonType>
のような凍結された UDT 列または凍結されたコレクションを宣言します。@UserDefinedType
: Cassandra ユーザー定義データ型 (UDT) を指定するために型レベルで適用されます。キースペースを指定すると、すべての DML および DDL 操作で、UDT 名の前にキースペースが付けられます。デフォルトでは、型は宣言から派生します。@Tuple
: 型をマップされたタプルとして使用するために型レベルで適用されます。@Element
: マップされたタプル内の要素またはフィールドの序数を指定するためにフィールドレベルで適用されます。デフォルトでは、型はプロパティ宣言から派生します。コンストラクターの引数で使用して、コンストラクターの作成中にタプル要素の序数をカスタマイズできます。@Version
: フィールドレベルで適用されると、オプティミスティックロックに使用され、保存操作時に変更がチェックされます。初期値はzero
で、更新のたびに自動的に増加します。
マッピングメタデータインフラストラクチャは、テクノロジーにもデータストアにも依存しない別の spring-data-commons プロジェクトで定義されます。
次の例は、より複雑なマッピングを示しています。
Person
クラス @Table("my_person")
public class Person {
@PrimaryKeyClass
public static class Key implements Serializable {
@PrimaryKeyColumn(ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private String type;
@PrimaryKeyColumn(ordinal = 1, type = PrimaryKeyType.PARTITIONED)
private String value;
@PrimaryKeyColumn(name = "correlated_type", ordinal = 2, type = PrimaryKeyType.CLUSTERED)
private String correlatedType;
// other getters/setters omitted
}
@PrimaryKey
private Person.Key key;
@CassandraType(type = CassandraType.Name.VARINT)
private Integer ssn;
@Column("f_name")
private String firstName;
@Column
@Indexed
private String lastName;
private Address address;
@CassandraType(type = CassandraType.Name.UDT, userTypeName = "myusertype")
private UdtValue usertype;
private Coordinates coordinates;
@Transient
private Integer accountTotal;
@CassandraType(type = CassandraType.Name.SET, typeArguments = CassandraType.Name.BIGINT)
private Set<Long> timestamps;
private Map<@Indexed String, InetAddress> sessions;
public Person(Integer ssn) {
this.ssn = ssn;
}
public Person.Key getKey() {
return key;
}
// no setter for Id. (getter is only exposed for some unit testing)
public Integer getSsn() {
return ssn;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
// other getters/setters omitted
}
次の例は、UDT Address
をマップする方法を示しています。
Address
@UserDefinedType("address")
public class Address {
@CassandraType(type = CassandraType.Name.VARCHAR)
private String street;
private String city;
private Set<String> zipcodes;
@CassandraType(type = CassandraType.Name.SET, typeArguments = CassandraType.Name.BIGINT)
private List<Long> timestamps;
// other getters/setters omitted
}
ユーザー定義型を使用するには、マッピングコンテキストで構成された UserTypeResolver が必要です。UserTypeResolver の設定方法については、設定の章を参照してください。 |
次の例は、タプルをマップする方法を示しています。
@Tuple
class Coordinates {
@Element(0)
@CassandraType(type = CassandraType.Name.VARCHAR)
private String description;
@Element(1)
private long longitude;
@Element(2)
private long latitude;
// other getters/setters omitted
}
インデックスの作成
アプリケーションの起動時にセカンダリインデックスを作成する場合は、特定のエンティティプロパティに @Indexed
、@SaiIndexed
、または @SASI
のアノテーションを付けることができます。インデックスを作成すると、スカラー型、ユーザー定義型、コレクション型の単純なセカンダリインデックスが作成されます。
StandardAnalyzer
または NonTokenizingAnalyzer
(それぞれ @StandardAnalyzed
および @NonTokenizingAnalyzed
を使用) などのアナライザーを適用するように SASI インデックスを構成できます。
マップ型は ENTRY
、KEYS
、VALUES
インデックスを区別します。インデックスを作成すると、アノテーションが付けられた要素からインデックス型が派生します。次の例は、インデックスを作成するさまざまな方法を示しています。
@Table
class PersonWithIndexes {
@Id
private String key;
@SASI
@StandardAnalyzed
private String names;
@SaiIndexed
@VectorType(dimensions = 1536) // required for table generation
private Vector vector;
@Indexed("indexed_map")
private Map<String, String> entries;
private Map<@Indexed String, String> keys;
private Map<String, @Indexed String> values;
// …
}
|
セッションの初期化時にインデックスを作成すると、アプリケーションの起動時にパフォーマンスに重大な影響を与える可能性があります。 |