14.4.基于元数据的映射
要充分利用 Spring Data for Apache Cassandra 支持中的对象映射功能,您应该使用注释对映射的域对象进行@Table注释。这样做可以让类路径扫描器找到并预处理您的域对象以提取必要的元数据。仅使用带注释的实体来执行模式操作。在最坏的情况下, SchemaAction.RECREATE_DROP_UNUSED操作会删除您的表并丢失数据。以下示例显示了一个简单的域对象:
示例 106. 示例域对象
代码语言:javascript复制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注解告诉您要使用的卡珊德拉主键哪个属性映射器。复合主键可能需要稍微不同的数据模型。
14.4.1.使用主键
Cassandra 需要至少一个 CQL 表的分区键字段。一张表可以额外声明一个或多个集群键字段。当您的 CQL 表具有复合主键时,您必须创建一个@PrimaryKeyClass来定义复合主键的结构。在这种情况下,“复合主键”是指一个或多个分区列可选地与一个或多个集群列组合。
主键可以使用任何单一的简单 Cassandra 类型或映射的用户定义类型。不支持集合类型的主键。
简单的主键
一个简单的主键由实体类中的一个分区键字段组成。由于它只有一个字段,我们可以安全地假设它是一个分区键。以下清单显示了在 Cassandra 中定义的 CQL 表,主键为user_id:
示例 107. Cassandra 中定义的 CQL 表
代码语言:javascript复制CREATE TABLE user (
user_id text,
firstname text,
lastname text,
PRIMARY KEY (user_id))
;
以下示例显示了一个已注释的 Java 类,使其对应于前面清单中定义的 Cassandra:
示例 108. 带注释的实体
代码语言:javascript复制@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 for Apache Cassandra 以两种方式表示:
- 嵌入到一个实体中。
- 通过使用@PrimaryKeyClass.
组合键的最简单形式是具有一个分区键和一个集群键的键。
以下示例显示了一个 CQL 语句来表示表及其组合键:
示例 109.具有复合主键的 CQL 表
代码语言:javascript复制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. 以下示例显示了一个具有平面复合主键的类:
示例 110.使用平面复合主键
代码语言:javascript复制@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类型)一起使用,并在单个复杂对象中表示实体的身份。以下示例显示了一个复合主键类:
示例 111. 复合主键类
代码语言:javascript复制@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
}
以下示例显示了如何使用复合主键:
示例 112.使用复合主键
代码语言:javascript复制@Table(value = "login_event")
public class LoginEvent {
@PrimaryKey
private LoginEventKey key;
@Column("ip_address")
private String ipAddress;
// getters and setters omitted
}
14.4.2.嵌入式实体支持
嵌入式实体用于在 Java 域模型中设计值对象,其属性被展平到表中。在下面的示例中,您会看到User.name用@Embedded. 这样做的结果是 的所有属性UserName都被折叠到user由 3 列 ( user_id, firstname, lastname)组成的表格中。
嵌入的实体可能只包含简单的属性类型。不可能将嵌入的实体嵌套到另一个嵌入的实体中。
但是,如果firstname和lastname列值实际上null在结果集中,则整个属性name将null根据onEmptyof进行设置@Embedded,null当所有嵌套属性都为 时,该s 对象null。 与此行为相反,USE_EMPTY尝试使用默认构造函数或从结果集中接受可为空参数值的构造函数创建新实例。
Example 113. 嵌入对象的示例代码
代码语言:javascript复制public class User {
@PrimaryKey("user_id")
private String userId;
@Embedded(onEmpty = USE_NULL)
UserName name;
}
public class UserName {
private String firstname;
private String lastname;
}
属性是nulliffirstname和lastnameare null。使用onEmpty=USE_EMPTY实例化UserName一个潜在null其属性值。
您可以使用注释的可选prefix元素在实体中多次嵌入值对象@Embedded。此元素表示一个前缀,并附加到嵌入对象中的每个列名称。请注意,如果多个属性呈现为相同的列名称,则属性将相互覆盖。
利用快捷方式@Embedded.Nullable和@Embedded.Emptyfor@Embedded(onEmpty = USE_NULL)和@Embedded(onEmpty = USE_EMPTY)to 减少冗长,同时相应地设置 JSR-305 @javax.annotation.Nonnull。
代码语言:javascript复制public class MyEntity {
@Id
Integer id;
@Embedded.Nullable
EmbeddedEntity embeddedEntity;
}
的快捷方式@Embedded(onEmpty = USE_NULL)。
14.4.3.映射注释概述
所述MappingCassandraConverter可以使用元数据来驱动对象的映射中的行表卡桑德拉。注释概述如下:
- @Id:应用于领域或财产级别以标记用于身份目的的财产。
- @Table: 应用于类级别,表示该类是映射到数据库的候选。您可以指定存储对象的表的名称。
- @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 索引。
- @CassandraType: 在字段级别应用以指定 Cassandra 数据类型。默认情况下,类型派生自属性声明。
- @Frozen: 在字段级别应用于类类型和参数化类型。声明一个冻结的 UDT 列或冻结的集合,如List<@Frozen UserDefinedPersonType>.
- @UserDefinedType:在类型级别应用以指定 Cassandra 用户定义数据类型 (UDT)。默认情况下,类型派生自声明。
- @Tuple: 在类型级别应用以将类型用作映射元组。
- @Element: 在字段级别应用以指定映射元组中的元素或字段序数。默认情况下,类型派生自属性声明。可用于构造函数参数以在构造函数创建期间自定义元组元素序数。
- @Version:应用于字段级别用于乐观锁定并检查保存操作的修改。初始值是zero每次更新时自动触发的值。
映射元数据基础结构在独立的 spring-data-commons 项目中定义,该项目与技术和数据存储无关。
以下示例显示了更复杂的映射:
示例 114. 映射Person类
代码语言:javascript复制@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:
示例 115. 映射的用户定义类型 Address
代码语言:javascript复制@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。
以下示例显示了如何映射元组:
示例 116. 映射元组
代码语言:javascript复制@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或@SASI如果您希望在应用程序启动时创建二级索引来注释特定的实体属性。索引创建为标量类型、用户定义类型和集合类型创建简单的二级索引。
您可以配置 SASI 索引以应用分析器,例如StandardAnalyzer或NonTokenizingAnalyzer(分别使用 @StandardAnalyzed和@NonTokenizingAnalyzed)。
地图类型的区分ENTRY,KEYS以及VALUES指标。索引创建从带注释的元素派生索引类型。以下示例显示了多种创建索引的方法:
示例 117. 地图索引的变体
代码语言:javascript复制@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注释可以应用于嵌入式实体或沿与侧单个属性@Embedded注释,在这种情况下,嵌入的所有属性编索引。
会话初始化时的索引创建可能会对应用程序启动产生严重的性能影响。