对于最新的稳定版本,请使用 Spring Data Cassandra 4.3.1Spring中文文档

对于最新的稳定版本,请使用 Spring Data Cassandra 4.3.1Spring中文文档

丰富的对象映射支持由 提供。 具有丰富的元数据模型,该模型提供了将域对象映射到 CQL 表的完整功能集。MappingCassandraConverterMappingCassandraConverterSpring中文文档

映射元数据模型是通过在域对象上使用注释来填充的。 但是,基础结构不仅限于使用注释作为元数据的唯一来源。 还允许您通过遵循一组约定将域对象映射到表,而无需提供任何其他元数据。MappingCassandraConverterSpring中文文档

在本章中,我们将介绍 的功能,如何使用约定将域对象映射到表,以及如何使用基于注释的映射元数据覆盖这些约定。MappingCassandraConverterSpring中文文档

对象映射基础知识

本节介绍了 Spring Data 对象映射、对象创建、字段和属性访问、可变性和不可变性的基础知识。 请注意,本节仅适用于不使用底层数据存储(如 JPA)的对象映射的 Spring Data 模块。 此外,请务必查阅特定于存储的部分,了解特定于存储的对象映射,例如索引、自定义列或字段名称等。Spring中文文档

Spring Data 对象映射的核心职责是创建域对象的实例,并将存储原生数据结构映射到这些实例上。 这意味着我们需要两个基本步骤:Spring中文文档

  1. 使用公开的构造函数之一创建实例。Spring中文文档

  2. 实例填充,用于具体化所有公开的属性。Spring中文文档

对象创建

Spring Data 会自动尝试检测用于具体化该类型对象的持久实体的构造函数。 解析算法的工作原理如下:Spring中文文档

  1. 如果有一个静态工厂方法注释,则使用它。@PersistenceCreatorSpring中文文档

  2. 如果存在单个构造函数,则使用该构造函数。Spring中文文档

  3. 如果有多个构造函数,并且恰好有一个构造函数用 注释,则使用它。@PersistenceCreatorSpring中文文档

  4. 如果类型是 Java,则使用规范构造函数。RecordSpring中文文档

  5. 如果存在无参数构造函数,则使用它。 其他构造函数将被忽略。Spring中文文档

值解析假定构造函数/工厂方法参数名称与实体的属性名称匹配,即解析将像填充属性一样执行,包括映射中的所有自定义项(不同的数据存储列或字段名称等)。 这还需要类文件中提供的参数名称信息或构造函数上存在的注释。@ConstructorPropertiesSpring中文文档

可以使用 Spring Framework 的值注释和特定于存储的 SpEL 表达式来自定义值解析。 有关更多详细信息,请参阅有关商店特定映射的部分。@ValueSpring中文文档

对象创建内部

为了避免反射的开销,Spring Data 对象创建默认使用在运行时生成的工厂类,该类将直接调用域类构造函数。 即对于此示例,类型:Spring中文文档

class Person {
  Person(String firstname, String lastname) { … }
}

我们将在运行时创建一个语义上等同于此的工厂类:Spring中文文档

class PersonObjectInstantiator implements ObjectInstantiator {

  Object newInstance(Object... args) {
    return new Person((String) args[0], (String) args[1]);
  }
}

这为我们提供了 10% 的性能提升。 要使域类符合此类优化的条件,它需要遵守一组约束:Spring中文文档

如果这些条件中的任何一个匹配,Spring Data 将通过反射回退到实体实例化。Spring中文文档

物业人口

创建实体的实例后,Spring Data 将填充该类的所有剩余持久属性。 除非已由实体的构造函数填充(即通过其构造函数参数列表使用),否则将首先填充标识符属性以允许解析循环对象引用。 之后,将在实体实例上设置尚未由构造函数填充的所有非暂时性属性。 为此,我们使用以下算法:Spring中文文档

  1. 如果属性是不可变的,但公开了一个方法(见下文),我们将使用该方法创建一个具有新属性值的新实体实例。with…with…Spring中文文档

  2. 如果定义了属性访问(即通过 getter 和 setter 的访问),我们将调用 setter 方法。Spring中文文档

  3. 如果属性是可变的,我们直接设置字段。Spring中文文档

  4. 如果该属性是不可变的,我们将使用持久性操作使用的构造函数(请参阅对象创建)来创建实例的副本。Spring中文文档

  5. 默认情况下,我们直接设置字段值。Spring中文文档

物业人口内部结构

对象构造的优化类似,我们也使用 Spring Data 运行时生成的访问器类与实体实例进行交互。Spring中文文档

class Person {

  private final Long id;
  private String firstname;
  private @AccessType(Type.PROPERTY) String lastname;

  Person() {
    this.id = null;
  }

  Person(Long id, String firstname, String lastname) {
    // Field assignments
  }

  Person withId(Long id) {
    return new Person(id, this.firstname, this.lastame);
  }

  void setLastname(String lastname) {
    this.lastname = lastname;
  }
}
生成的属性访问器
class PersonPropertyAccessor implements PersistentPropertyAccessor {

  private static final MethodHandle firstname;              (2)

  private Person person;                                    (1)

  public void setProperty(PersistentProperty property, Object value) {

    String name = property.getName();

    if ("firstname".equals(name)) {
      firstname.invoke(person, (String) value);             (2)
    } else if ("id".equals(name)) {
      this.person = person.withId((Long) value);            (3)
    } else if ("lastname".equals(name)) {
      this.person.setLastname((String) value);              (4)
    }
  }
}
1 PropertyAccessor 保存基础对象的可变实例。这是为了启用其他不可变属性的突变。
2 默认情况下,Spring Data 使用字段访问来读取和写入属性值。根据字段的可见性规则,用于与字段交互。privateMethodHandles
3 该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。调用将创建一个新对象。所有后续突变都将在新实例中发生,而前一个实例保持不变。withId(…)withId(…)Person
4 使用属性访问允许直接调用方法,而无需使用 。MethodHandles

这为我们提供了 25% 的性能提升。 要使域类符合此类优化的条件,它需要遵守一组约束:Spring中文文档

默认情况下,Spring Data 尝试使用生成的属性访问器,如果检测到限制,则回退到基于反射的属性访问器。Spring中文文档

让我们看一下以下实体:Spring中文文档

示例实体
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 标识符属性是最终属性,但在构造函数中设置为。 该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。 创建新实例时,原始实例保持不变。 相同的模式通常应用于存储管理的其他属性,但可能需要更改持久性操作。 wither 方法是可选的,因为持久性构造函数(参见 6)实际上是一个复制构造函数,并且设置属性将转换为使用应用新标识符值创建新实例。nullwithId(…)Person
2 和 属性是可能通过 getter 公开的普通不可变属性。firstnamelastname
3 该属性是不可变的,但派生自该属性。 在所示的设计中,数据库值将优先于默认值,因为 Spring Data 使用唯一声明的构造函数。 即使意图是计算应该是首选,也很重要的是,此构造函数也必须将其作为参数(以可能忽略它),否则属性填充步骤将尝试设置年龄字段并失败,因为它是不可变的,并且不存在任何方法。agebirthdayagewith…
4 该属性是可变的,通过直接设置其字段来填充。comment
5 该属性是可变的,并通过调用 setter 方法进行填充。remarks
6 该类公开用于创建对象的工厂方法和构造函数。 这里的核心思想是使用工厂方法而不是其他构造函数,以避免需要通过 来消除构造函数的歧义。 相反,属性的默认值是在工厂方法中处理的。 如果希望 Spring Data 使用工厂方法进行对象实例化,请使用 .@PersistenceCreator@PersistenceCreator

一般建议

  • 尝试坚持使用不可变对象 — 不可变对象很容易创建,因为具体化对象只需调用其构造函数即可。 此外,这样还可以避免域对象充斥着允许客户端代码操作对象状态的 setter 方法。 如果需要这些,最好将它们设置为包保护,以便它们只能由有限数量的共置类型调用。 仅构造函数的实现速度比属性填充快 30%。Spring中文文档

  • 提供 all-args 构造函数 — 即使不能或不想将实体建模为不可变值,提供将实体的所有属性作为参数(包括可变属性)的构造函数仍然有价值,因为这允许对象映射跳过属性填充以获得最佳性能。Spring中文文档

  • 使用工厂方法而不是重载构造函数来避免@PersistenceCreator — 由于需要全参数构造函数来获得最佳性能,我们通常希望公开更多特定于应用程序用例的构造函数,这些构造函数省略了自动生成的标识符等内容。 使用静态工厂方法来公开 all-args 构造函数的这些变体是一种既定模式。Spring中文文档

  • 确保遵守允许使用生成的实例化器和属性访问器类的约束Spring中文文档

  • 对于要生成的标识符,仍将 final 字段与全参数持久性构造函数(首选)或 with... 方法结合使用 —Spring中文文档

  • 使用 Lombok 来避免样板代码 — 由于持久化操作通常需要构造函数获取所有参数,因此它们的声明会成为对字段赋值的样板参数的乏味重复,而使用 Lombok 的 .@AllArgsConstructorSpring中文文档

覆盖属性

Java 允许灵活地设计域类,其中子类可以定义已在其超类中声明的相同名称的属性。 请看以下示例:Spring中文文档

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

这两个类都定义一个使用可分配的类型。 然而阴影. 根据类设计,使用构造函数可能是设置 的唯一默认方法。 或者,调用 setter 可以设置 in . 所有这些机制在某种程度上都会产生冲突,因为这些属性共享相同的名称,但可能表示两个不同的值。 如果类型不可赋值,Spring Data 会跳过超类型属性。 也就是说,重写属性的类型必须可分配给其超类型属性类型才能注册为重写,否则超类型属性将被视为瞬态属性。 我们通常建议使用不同的属性名称。fieldSubTypeSuperType.fieldSuperType.fieldsuper.setField(…)fieldSuperTypeSpring中文文档

Spring Data 模块通常支持包含不同值的覆盖属性。 从编程模型的角度来看,有几件事需要考虑:Spring中文文档

  1. 应保留哪个属性(默认为所有声明的属性)? 您可以通过使用 对属性进行批注来排除这些属性。@TransientSpring中文文档

  2. 如何表示数据存储中的属性? 对不同的值使用相同的字段/列名称通常会导致数据损坏,因此应使用显式字段/列名称至少批注一个属性。Spring中文文档

  3. 不能使用,因为通常不能在不对 setter 实现进行任何进一步假设的情况下设置超属性。@AccessType(PROPERTY)Spring中文文档

Kotlin 支持

Spring Data 调整了 Kotlin 的细节,以允许对象创建和更改。Spring中文文档

Kotlin 对象创建

支持实例化 Kotlin 类,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。Spring中文文档

Spring Data 会自动尝试检测用于具体化该类型对象的持久实体的构造函数。 解析算法的工作原理如下:Spring中文文档

  1. 如果存在用 注释的构造函数,则使用该构造函数。@PersistenceCreatorSpring中文文档

  2. 如果类型是 Kotlin 数据类,则使用主构造函数。Spring中文文档

  3. 如果有一个静态工厂方法注释,则使用它。@PersistenceCreatorSpring中文文档

  4. 如果存在单个构造函数,则使用该构造函数。Spring中文文档

  5. 如果有多个构造函数,并且恰好有一个构造函数用 注释,则使用它。@PersistenceCreatorSpring中文文档

  6. 如果类型是 Java,则使用规范构造函数。RecordSpring中文文档

  7. 如果存在无参数构造函数,则使用它。 其他构造函数将被忽略。Spring中文文档

考虑以下类:dataPersonSpring中文文档

data class Person(val id: String, val name: String)

上面的类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义这个类,并用它进行注释以指示构造函数首选项:@PersistenceCreatorSpring中文文档

data class Person(var id: String, val name: String) {

    @PersistenceCreator
    constructor(id: String) : this(id, "unknown")
}

Kotlin 支持参数可选性,允许在未提供参数时使用默认值。 当 Spring Data 检测到参数默认值的构造函数时,如果数据存储没有提供值(或只是返回),则这些参数不存在,因此 Kotlin 可以应用参数默认值。请考虑以下应用参数默认值的类nullnameSpring中文文档

data class Person(var id: String, val name: String = "unknown")

每当参数不是结果的一部分或其值为 时,默认值为 。namenullnameunknownSpring中文文档

Kotlin 数据类的属性填充

在 Kotlin 中,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。 考虑以下类:dataPersonSpring中文文档

data class Person(val id: String, val name: String)

此类实际上是不可变的。 它允许创建新实例,因为 Kotlin 会生成一个创建新对象实例的方法,从现有对象复制所有属性值,并将作为参数提供的属性值应用于该方法。copy(…)Spring中文文档

Kotlin 覆盖属性

Kotlin 允许声明属性覆盖以更改子类中的属性。Spring中文文档

open class SuperType(open var field: Int)

class SubType(override var field: Int = 1) :
	SuperType(field) {
}

这样的排列呈现两个名称为 的属性。 Kotlin 为每个类中的每个属性生成属性访问器(getter 和 setter)。 实际上,代码如下所示:fieldSpring中文文档

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;
   }
}

Getters 和 Setters 只在片场而不是 . 在这样的安排中,使用构造函数是设置 的唯一默认方法。 可以添加 to set via 的方法,但超出了支持的约定。 属性重写在某种程度上会产生冲突,因为属性共享相同的名称,但可能表示两个不同的值。 我们通常建议使用不同的属性名称。SubTypeSubType.fieldSuperType.fieldSuperType.fieldSubTypeSuperType.fieldthis.SuperType.field = …Spring中文文档

Spring Data 模块通常支持包含不同值的覆盖属性。 从编程模型的角度来看,有几件事需要考虑:Spring中文文档

  1. 应保留哪个属性(默认为所有声明的属性)? 您可以通过使用 对属性进行批注来排除这些属性。@TransientSpring中文文档

  2. 如何表示数据存储中的属性? 对不同的值使用相同的字段/列名称通常会导致数据损坏,因此应使用显式字段/列名称至少批注一个属性。Spring中文文档

  3. 不能使用,因为无法设置超级属性。@AccessType(PROPERTY)Spring中文文档

Kotlin 值类

Kotlin 值类专为更具表现力的领域模型而设计,以明确基本概念。 Spring Data 可以读取和写入使用值类定义属性的类型。Spring中文文档

请考虑以下域模型:Spring中文文档

@JvmInline
value class EmailAddress(val theAddress: String)                                    (1)

data class Contact(val id: String, val name:String, val emailAddress: EmailAddress) (2)
1 具有不可为 null 值类型的简单值类。
2 数据类使用 value 类定义属性。EmailAddress
使用非基元值类型的不可为 null 属性在 compiled 类中展平为值类型。 可为 null 的基元值类型或可为 null 的值中值类型用其包装器类型表示,这会影响值类型在数据库中的表示方式。
1 PropertyAccessor 保存基础对象的可变实例。这是为了启用其他不可变属性的突变。
2 默认情况下,Spring Data 使用字段访问来读取和写入属性值。根据字段的可见性规则,用于与字段交互。privateMethodHandles
3 该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。调用将创建一个新对象。所有后续突变都将在新实例中发生,而前一个实例保持不变。withId(…)withId(…)Person
4 使用属性访问允许直接调用方法,而无需使用 。MethodHandles
1 标识符属性是最终属性,但在构造函数中设置为。 该类公开用于设置标识符的方法,例如,当实例插入数据存储并生成标识符时。 创建新实例时,原始实例保持不变。 相同的模式通常应用于存储管理的其他属性,但可能需要更改持久性操作。 wither 方法是可选的,因为持久性构造函数(参见 6)实际上是一个复制构造函数,并且设置属性将转换为使用应用新标识符值创建新实例。nullwithId(…)Person
2 和 属性是可能通过 getter 公开的普通不可变属性。firstnamelastname
3 该属性是不可变的,但派生自该属性。 在所示的设计中,数据库值将优先于默认值,因为 Spring Data 使用唯一声明的构造函数。 即使意图是计算应该是首选,也很重要的是,此构造函数也必须将其作为参数(以可能忽略它),否则属性填充步骤将尝试设置年龄字段并失败,因为它是不可变的,并且不存在任何方法。agebirthdayagewith…
4 该属性是可变的,通过直接设置其字段来填充。comment
5 该属性是可变的,并通过调用 setter 方法进行填充。remarks
6 该类公开用于创建对象的工厂方法和构造函数。 这里的核心思想是使用工厂方法而不是其他构造函数,以避免需要通过 来消除构造函数的歧义。 相反,属性的默认值是在工厂方法中处理的。 如果希望 Spring Data 使用工厂方法进行对象实例化,请使用 .@PersistenceCreator@PersistenceCreator
1 具有不可为 null 值类型的简单值类。
2 数据类使用 value 类定义属性。EmailAddress
使用非基元值类型的不可为 null 属性在 compiled 类中展平为值类型。 可为 null 的基元值类型或可为 null 的值中值类型用其包装器类型表示,这会影响值类型在数据库中的表示方式。

数据映射和类型转换

本节介绍如何将类型映射到 Apache Cassandra 表示形式以及从 Apache Cassandra 表示形式映射到这些表示形式。Spring中文文档

Spring Data for Apache Cassandra 支持 Apache Cassandra 提供的多种类型。 除了这些类型之外,Spring Data for Apache Cassandra 还提供了一组内置转换器来映射其他类型。 您可以提供自己的自定义转换器来调整类型转换。 有关详细信息,请参阅“使用自定义转换器覆盖默认映射”。 下表将 Spring Data 类型映射到 Cassandra 类型:Spring中文文档

表 1.类型
类型 Cassandra 类型

StringSpring中文文档

text(默认)、、varcharasciiSpring中文文档

double,DoubleSpring中文文档

doubleSpring中文文档

float,FloatSpring中文文档

floatSpring中文文档

long,LongSpring中文文档

bigint(默认),counterSpring中文文档

int,IntegerSpring中文文档

intSpring中文文档

short,ShortSpring中文文档

smallintSpring中文文档

byte,ByteSpring中文文档

tinyintSpring中文文档

boolean,BooleanSpring中文文档

booleanSpring中文文档

BigIntegerSpring中文文档

varintSpring中文文档

BigDecimalSpring中文文档

decimalSpring中文文档

java.util.DateSpring中文文档

timestampSpring中文文档

com.datastax.driver.core.LocalDateSpring中文文档

dateSpring中文文档

InetAddressSpring中文文档

inetSpring中文文档

ByteBufferSpring中文文档

blobSpring中文文档

java.util.UUIDSpring中文文档

uuidSpring中文文档

TupleValue、映射的元组类型Spring中文文档

tuple<…>Spring中文文档

UDTValue、映射的用户定义类型Spring中文文档

用户类型Spring中文文档

java.util.Map<K, V>Spring中文文档

mapSpring中文文档

java.util.List<E>Spring中文文档

listSpring中文文档

java.util.Set<E>Spring中文文档

setSpring中文文档

EnumSpring中文文档

text(默认)、、、、、bigintvarintintsmallinttinyintSpring中文文档

LocalDate
(Joda、Java 8、JSR310-BackPort)Spring中文文档

dateSpring中文文档

LocalTime+ (Joda、Java 8、JSR310-BackPort)Spring中文文档

timeSpring中文文档

LocalDateTime
、 (Joda、Java 8、JSR310-BackPort)
LocalTimeInstantSpring中文文档

timestampSpring中文文档

ZoneId(Java 8,JSR310-BackPort)Spring中文文档

textSpring中文文档

每个受支持的类型都映射到默认的 Cassandra 数据类型。 Java 类型可以通过使用 映射到其他 Cassandra 类型,如以下示例所示:@CassandraTypeSpring中文文档

例 1.枚举映射到数值类型
@Table
public class EnumToOrdinalMapping {

  @PrimaryKey String id;

  @CassandraType(type = Name.INT) Condition asOrdinal;
}

public enum Condition {
  NEW, USED
}
表 1.类型
类型 Cassandra 类型

StringSpring中文文档

text(默认)、、varcharasciiSpring中文文档

double,DoubleSpring中文文档

doubleSpring中文文档

float,FloatSpring中文文档

floatSpring中文文档

long,LongSpring中文文档

bigint(默认),counterSpring中文文档

int,IntegerSpring中文文档

intSpring中文文档

short,ShortSpring中文文档

smallintSpring中文文档

byte,ByteSpring中文文档

tinyintSpring中文文档

boolean,BooleanSpring中文文档

booleanSpring中文文档

BigIntegerSpring中文文档

varintSpring中文文档

BigDecimalSpring中文文档

decimalSpring中文文档

java.util.DateSpring中文文档

timestampSpring中文文档

com.datastax.driver.core.LocalDateSpring中文文档

dateSpring中文文档

InetAddressSpring中文文档

inetSpring中文文档

ByteBufferSpring中文文档

blobSpring中文文档

java.util.UUIDSpring中文文档

uuidSpring中文文档

TupleValue、映射的元组类型Spring中文文档

tuple<…>Spring中文文档

UDTValue、映射的用户定义类型Spring中文文档

用户类型Spring中文文档

java.util.Map<K, V>Spring中文文档

mapSpring中文文档

java.util.List<E>Spring中文文档

listSpring中文文档

java.util.Set<E>Spring中文文档

setSpring中文文档

EnumSpring中文文档

text(默认)、、、、、bigintvarintintsmallinttinyintSpring中文文档

LocalDate
(Joda、Java 8、JSR310-BackPort)Spring中文文档

dateSpring中文文档

LocalTime+ (Joda、Java 8、JSR310-BackPort)Spring中文文档

timeSpring中文文档

LocalDateTime
、 (Joda、Java 8、JSR310-BackPort)
LocalTimeInstantSpring中文文档

timestampSpring中文文档

ZoneId(Java 8,JSR310-BackPort)Spring中文文档

textSpring中文文档

基于约定的映射

MappingCassandraConverter使用一些约定在未提供其他映射元数据时将域对象映射到 CQL 表。 约定是:Spring中文文档

  • 简单(短)Java 类名通过更改为小写字母映射到表名。 例如,映射到名为 的表。com.bigbank.SavingsAccountsavingsaccountSpring中文文档

  • 转换器使用任何已注册的 Spring 实例来覆盖对象属性到表列的默认映射。ConverterSpring中文文档

  • 对象的属性用于在表中的列之间进行转换。Spring中文文档

您可以通过配置 on 来调整约定。 命名策略对象实现从实体类和实际属性派生表、列或用户定义类型的约定。NamingStrategyCassandraMappingContextSpring中文文档

以下示例演示如何配置:NamingStrategySpring中文文档

例 2.配置NamingStrategyCassandraMappingContext
		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));

映射配置

除非显式配置,否则在创建 . 您可以创建自己的实例,告诉它在启动时扫描域类的类路径的位置,以提取元数据并构造索引。MappingCassandraConverterCassandraTemplateMappingCassandraConverterSpring中文文档

此外,通过创建自己的实例,您可以注册 Spring 实例以用于将特定类映射到数据库或从数据库映射特定类。 以下示例配置类设置 Cassandra 映射支持:ConverterSpring中文文档

例 3.@Configuration类来配置 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需要您实现定义键空间的方法。 还有一个名为 的方法。 您可以覆盖它,告诉转换器在哪里扫描带有注释的类。AbstractCassandraConfigurationgetEntityBasePackages(…)@TableSpring中文文档

您可以通过重写该方法向 添加其他转换器。MappingCassandraConvertercustomConversionsSpring中文文档

AbstractCassandraConfiguration创建一个实例,并将其注册到名为 的容器中。CassandraTemplatecassandraTemplate
AbstractCassandraConfiguration创建一个实例,并将其注册到名为 的容器中。CassandraTemplatecassandraTemplate

基于元数据的映射

要充分利用 Spring Data for Apache Cassandra 支持中的对象映射功能,您应该使用注释对映射的域对象进行注释。 这样做可以让类路径扫描程序查找并预处理域对象,以提取必要的元数据。 只有带批注的实体用于执行架构操作。 在最坏的情况下,操作会删除您的表,并且您会丢失数据。 下面的示例演示一个简单的域对象:@TableSchemaAction.RECREATE_DROP_UNUSEDSpring中文文档

例 4.示例域对象
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;
}
注释告诉映射器要用于 Cassandra 主键的属性。 复合主键可能需要略有不同的数据模型。@Id

使用主键

Cassandra 要求 CQL 表至少有一个分区键字段。 表还可以声明一个或多个聚类键字段。 当 CQL 表具有复合主键时,必须创建一个来定义复合主键的结构。 在此上下文中,“复合主键”是指一个或多个分区列,可以选择与一个或多个聚类列组合在一起。@PrimaryKeyClassSpring中文文档

主键可以使用任何单一的简单 Cassandra 类型或映射的用户定义类型。 不支持集合类型的主键。Spring中文文档

简单主键

简单主键由实体类中的一个分区键字段组成。 由于它只是一个字段,我们可以安全地假设它是一个分区键。 以下列表显示了在 Cassandra 中定义的 CQL 表,其主键为 :user_idSpring中文文档

例 5.Cassandra 中定义的 CQL 表
CREATE TABLE user (
  user_id text,
  firstname text,
  lastname text,
  PRIMARY KEY (user_id))
;

以下示例显示了一个 Java 类,该类经过注释,使其对应于上一个列表中定义的 Cassandra:Spring中文文档

例 6.带注释的实体
@Table(value = "login_event")
public class LoginEvent {

  @PrimaryKey("user_id")
  private String userId;

  private String firstname;
  private String lastname;

  // getters and setters omitted

}

复合键

复合主键(或复合键)由多个主键字段组成。 也就是说,复合主键可以由多个分区键、一个分区键和一个聚类键组成,也可以由多个主键字段组成。Spring中文文档

使用 Spring Data for Apache Cassandra 可以以两种方式表示复合键:Spring中文文档

组合键的最简单形式是具有一个分区键和一个群集键的键。Spring中文文档

以下示例显示了表示表及其复合键的 CQL 语句:Spring中文文档

例 7.具有复合主键的 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)
;

平面复合主键

平面复合主键作为平面字段嵌入到实体中。 主键字段用 . 选择需要查询包含各个字段的谓词或使用 . 下面的示例显示了一个具有平面复合主键的类:@PrimaryKeyColumnMapIdSpring中文文档

例 8.使用平面复合主键
@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
}

主键类

主键类是映射到实体的多个字段或属性的复合主键类。 它用 和 应该定义 和 方法进行注释。 这些方法的值相等语义应与键映射到的数据库类型的数据库相等性一致。 主键类可以与存储库一起使用(作为类型),并在单个复杂对象中表示实体的身份。 以下示例显示了一个复合主键类:@PrimaryKeyClassequalshashCodeIdSpring中文文档

例 9.复合主键类
@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
}

以下示例演示如何使用复合主键:Spring中文文档

例 10.使用复合主键
@Table(value = "login_event")
public class LoginEvent {

  @PrimaryKey
  private LoginEventKey key;

  @Column("ip_address")
  private String ipAddress;

  // getters and setters omitted
}

嵌入式实体支持

嵌入式实体用于设计 Java 域模型中的值对象,其属性被展平到表中。 在下面的示例中,您会看到 用 . 这样做的结果是,所有属性都被折叠到由 3 列 (, , ) 组成的表中。User.name@EmbeddedUserNameuseruser_idfirstnamelastnameSpring中文文档

嵌入实体只能包含简单的属性类型。 无法将一个嵌入实体嵌套到另一个嵌入实体中。Spring中文文档

但是,如果 和 列值实际上在结果集中,则当所有嵌套属性都为 时,整个属性将根据 的 设置为 ,其中 s 对象。
与此行为相反,尝试使用默认构造函数或接受结果集中的可为 null 参数值的构造函数创建新实例。
firstnamelastnamenullnamenullonEmpty@EmbeddednullnullUSE_EMPTYSpring中文文档

例 11.嵌入对象示例代码
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 属性为 if 和 are 。 用于实例化其属性的潜在值。nullfirstnamelastnamenullonEmpty=USE_EMPTYUserNamenull

您可以使用批注的可选元素在实体中多次嵌入值对象。 此元素表示前缀,并附加到嵌入对象中的每个列名。 请注意,如果多个属性呈现为相同的列名,则属性将相互覆盖。prefix@EmbeddedSpring中文文档

使用快捷方式和 for 和 来减少冗长,并同时相应地设置 JSR-305。@Embedded.Nullable@Embedded.Empty@Embedded(onEmpty = USE_NULL)@Embedded(onEmpty = USE_EMPTY)@javax.annotation.NonnullSpring中文文档

public class MyEntity {

    @Id
    Integer id;

    @Embedded.Nullable (1)
    EmbeddedEntity embeddedEntity;
}
1 的快捷方式。@Embedded(onEmpty = USE_NULL)

映射注释概述

可以使用元数据来驱动对象到 Cassandra 表中行的映射。 注释概述如下:MappingCassandraConverterSpring中文文档

  • @Id:应用于字段或属性级别,以标记用于标识目的的属性。Spring中文文档

  • @Table:在类级别应用,以指示此类是映射到数据库的候选项。 您可以指定存储对象的表的名称。Spring中文文档

  • @PrimaryKey:类似于,但允许您指定列名。@IdSpring中文文档

  • @PrimaryKeyColumn:特定于主键列的 Cassandra 注释,允许您指定主键列属性,例如用于聚集或分区。 可用于单个和多个属性,以指示单个或复合(复合)主键。 如果用于实体内的属性,请确保同时应用注释。@IdSpring中文文档

  • @PrimaryKeyClass:在类级别应用,以指示此类是复合主键类。 必须在实体类中引用。@PrimaryKeySpring中文文档

  • @Transient:默认情况下,所有私有字段都映射到该行。 此批注将应用它的字段排除在数据库中。 暂时性属性不能在持久性构造函数中使用,因为转换器无法实现构造函数参数的值。Spring中文文档

  • @PersistenceConstructor:标记给定的构造函数(甚至是受包保护的构造函数),以便在从数据库实例化对象时使用。 构造函数参数按名称映射到检索到的行中的键值。Spring中文文档

  • @Value:此注解是 Spring 框架的一部分。在映射框架中,它可以应用于构造函数参数。 这允许您使用 Spring Expression Language 语句来转换在数据库中检索到的键的值,然后再将其用于构造域对象。 为了引用给定文档的属性 // 必须使用如下表达式: where refer to the root of the given document.RowUdtValueTupleValue@Value("#root.getString(0)")rootSpring中文文档

  • @ReadOnlyProperty:应用于字段级别以将属性标记为只读。 实体绑定的 insert 和 update 语句不包括此属性。Spring中文文档

  • @Column:应用于现场级别。 描述列名,因为它在 Cassandra 表中表示,从而使名称与类的字段名称不同。 可用于构造函数参数,以在构造函数创建期间自定义列名。Spring中文文档

  • @Embedded:应用于现场级别。 为映射到表或用户定义类型的类型启用嵌入对象使用。 嵌入对象的属性将展平到其父级的结构中。Spring中文文档

  • @Indexed:应用于现场级别。 描述要在会话初始化时创建的索引。Spring中文文档

  • @SASI:应用于现场级别。 允许在会话初始化期间创建 SASI 索引。Spring中文文档

  • @CassandraType:应用于字段级别以指定 Cassandra 数据类型。 默认情况下,类型派生自属性声明。Spring中文文档

  • @Frozen:在字段级别应用于类类型和参数化类型。 声明冻结的 UDT 列或冻结的集合,如 .List<@Frozen UserDefinedPersonType>Spring中文文档

  • @UserDefinedType:在类型级别应用以指定 Cassandra 用户定义的数据类型 (UDT)。 默认情况下,类型派生自声明。Spring中文文档

  • @Tuple:在类型级别应用,以将类型用作映射元组。Spring中文文档

  • @Element:应用于字段级别,以指定映射元组中的元素或字段序号。 默认情况下,类型派生自属性声明。 可用于构造函数参数,以在构造函数创建期间自定义元组元素序号。Spring中文文档

  • @Version:应用于字段级别用于乐观锁定,并检查保存操作的修改。 初始值是在每次更新时自动增加的值。zeroSpring中文文档

映射元数据基础结构在单独的spring-data-commons项目中定义,该项目与技术和数据存储无关。Spring中文文档

以下示例显示了更复杂的映射:Spring中文文档

例 12.映射类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:AddressSpring中文文档

例 13.映射的用户定义类型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
}
使用用户定义类型需要使用映射上下文进行配置。 有关如何配置 .UserTypeResolverUserTypeResolver

以下示例演示如何映射元组:Spring中文文档

例 14.映射元组
@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@SASISpring中文文档

您可以配置 SASI 索引以应用分析器,例如 或(分别使用 和 )。StandardAnalyzerNonTokenizingAnalyzer@StandardAnalyzed@NonTokenizingAnalyzedSpring中文文档

映射类型区分 、 和 索引。 索引创建从带批注的元素派生索引类型。 以下示例演示了创建索引的多种方法:ENTRYKEYSVALUESSpring中文文档

例 15.地图索引的变体
@Table
class PersonWithIndexes {

  @Id
  private String key;

  @SASI
  @StandardAnalyzed
  private String names;

  @Indexed("indexed_map")
  private Map<String, String> entries;

  private Map<@Indexed String, String> keys;

  private Map<String, @Indexed String> values;

  // …
}

注释可以应用于嵌入实体的单个属性,也可以与注释一起应用,在这种情况下,嵌入的所有属性都将被索引。@Indexed@EmbeddedSpring中文文档

在会话初始化时创建索引可能会对应用程序启动产生严重性能影响。
注释告诉映射器要用于 Cassandra 主键的属性。 复合主键可能需要略有不同的数据模型。@Id

嵌入实体只能包含简单的属性类型。 无法将一个嵌入实体嵌套到另一个嵌入实体中。Spring中文文档

1 属性为 if 和 are 。 用于实例化其属性的潜在值。nullfirstnamelastnamenullonEmpty=USE_EMPTYUserNamenull

使用快捷方式和 for 和 来减少冗长,并同时相应地设置 JSR-305。@Embedded.Nullable@Embedded.Empty@Embedded(onEmpty = USE_NULL)@Embedded(onEmpty = USE_EMPTY)@javax.annotation.NonnullSpring中文文档

public class MyEntity {

    @Id
    Integer id;

    @Embedded.Nullable (1)
    EmbeddedEntity embeddedEntity;
}
1 的快捷方式。@Embedded(onEmpty = USE_NULL)
1 的快捷方式。@Embedded(onEmpty = USE_NULL)
使用用户定义类型需要使用映射上下文进行配置。 有关如何配置 .UserTypeResolverUserTypeResolver

注释可以应用于嵌入实体的单个属性,也可以与注释一起应用,在这种情况下,嵌入的所有属性都将被索引。@Indexed@EmbeddedSpring中文文档

在会话初始化时创建索引可能会对应用程序启动产生严重性能影响。