14. 弹簧数据云数据存储
此集成与数据存储模式下的 Firestore 完全兼容,但与本机模式下的 Firestore 不兼容。 |
Spring 数据是用于在众多存储技术中存储和检索 POJO 的抽象。 Spring Cloud GCP 在数据存储模式下为Google Cloud Firestore添加了 Spring Data 支持。
Maven 仅使用此模块的坐标,使用Spring Cloud GCP BOM:
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-data-datastore</artifactId>
</dependency>
格拉德尔坐标:
dependencies {
implementation("com.google.cloud:spring-cloud-gcp-data-datastore")
}
我们为 SpringData Data Store 提供了一个 Spring 引导启动器,您可以使用我们推荐的自动配置设置。 要使用启动器,请参阅下面的坐标。
专家:
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
</dependency>
格拉德尔:
dependencies {
implementation("com.google.cloud:spring-cloud-gcp-starter-data-datastore")
}
此设置还负责引入云 Java 云数据存储库的最新兼容版本。
14.1. 配置
要设置 Spring 数据云数据存储,您必须配置以下内容:
- 设置与 Google Cloud 数据存储的连接详细信息。
14.1.1. 云数据存储设置
您可以使用Spring Data Data Data Store 的 Spring Boot Starter 在 Spring应用程序中自动配置 Google Cloud Datastore。 它包含所有必要的设置,使您可以轻松地使用Google云项目进行身份验证。 以下配置选项可用:
名字 | 描述 | 必填 | 默认值 |
| 启用云数据存储客户端 | 不 | |
| 托管 Google Cloud 数据存储 API 的 GCP 项目 ID(如果与Spring Cloud GCP 核心模块中的 ID 不同) | 不 | |
| 用于使用 Google Cloud Datastore API 进行身份验证的 OAuth2 凭据(如果与Spring Cloud GCP Core Module 中的凭据不同) | 不 | |
| Base64 编码的 OAuth2 凭据,用于使用 Google Cloud Datastore API 进行身份验证(如果与Spring Cloud GCP Core Module 中的凭据不同) | 不 | |
| Spring Cloud GCP 云数据存储凭据的 OAuth2 范围 | 不 | www.googleapis.com/auth/datastore |
| 要使用的云数据存储命名空间 | 不 | GCP 项目中云数据存储的默认命名空间 |
| 要连接到的数据存储服务或仿真器。可用于连接到手动启动的数据存储模拟器。如果启用了自动配置的模拟器,则将忽略此属性并使用此属性。hostname:portlocalhost:<emulator_port> | 不 | |
| 启用自动配置以启动数据存储模拟器的本地实例。 | 不 | |
| 用于数据存储模拟器的本地端口 | 不 | |
| 用于数据存储模拟器实例的一致性 | 不 | |
| 配置模拟器是否应将任何数据保存到磁盘。 | 不 | |
| 用于存储/检索模拟器运行的数据/配置的目录。 | 不 | 默认值为。请参阅gcloud 文档以查找您的。 |
14.1.2. 仓库设置
Spring 数据存储库可以通过主类上的注释进行配置。 使用我们的 Spring Boot Starter for Spring Data Cloud Datastore,会自动添加。 不需要将其添加到任何其他类中,除非需要覆盖 @EnableDatastoreRepositories 提供的更细粒度的配置参数。@EnableDatastoreRepositories
@Configuration
@EnableDatastoreRepositories
14.1.3. 自动配置
我们的 Spring 引导自动配置在 Spring 应用程序上下文中创建以下可用的 bean:
- 的实例
DatastoreTemplate
- 启用存储库时扩展的所有用户定义的存储库的实例,以及(具有其他云数据存储功能的扩展)
CrudRepository
PagingAndSortingRepository
DatastoreRepository
PagingAndSortingRepository
- 来自 Google Cloud Java Client for Datastore 的实例,用于方便和较低级别的 API 访问
Datastore
14.1.4. 数据存储模拟器自动配置
此 Spring 引导自动配置还可以配置和启动本地数据存储仿真器服务器(如果由属性启用)。
它对于集成测试很有用,但对生产不起作用。
启用后,将忽略该属性,并强制数据存储自动配置本身连接到自动配置的本地仿真器实例。spring.cloud.gcp.datastore.host
它将创建一个实例作为 bean,该 bean 存储以获取客户端连接到模拟器以方便使用,并为本地访问创建较低级别的 API。 在 Spring 应用程序上下文关闭后,模拟器将正确停止。LocalDatastoreHelper
DatastoreOptions
Datastore
14.2. 对象映射
Spring 数据云数据存储允许您通过注释将域 POJO 映射到云数据存储类型和实体:
@Entity(name = "traders")
public class Trader {
@Id
@Field(name = "trader_id")
String traderId;
String firstName;
String lastName;
@Transient
Double temporaryNumber;
}
Spring 数据云数据存储将忽略任何带有注释的属性。 这些属性不会写入或读取云数据存储。@Transient
14.2.1. 构造函数
POJO 支持简单的构造函数。 构造函数参数可以是持久性属性的子集。 每个构造函数参数都需要具有与实体上的持久属性相同的名称和类型,构造函数应从给定参数设置属性。 不支持未直接设置为属性的参数。
@Entity(name = "traders")
public class Trader {
@Id
@Field(name = "trader_id")
String traderId;
String firstName;
String lastName;
@Transient
Double temporaryNumber;
public Trader(String traderId, String firstName) {
this.traderId = traderId;
this.firstName = firstName;
}
}
14.2.2. 种类
注释可以提供存储带注释类的实例的云数据存储类型的名称,每行一个。@Entity
14.2.3. 密钥
@Id
标识与 ID 值对应的属性。
您必须将 POJO 的一个字段注释为 ID 值,因为 Cloud 数据存储中的每个实体都需要一个 ID 值:
@Entity(name = "trades")
public class Trade {
@Id
@Field(name = "trade_id")
String tradeId;
@Field(name = "trader_id")
String traderId;
String action;
Double price;
Double shares;
String symbol;
}
数据存储可以自动分配整数 ID 值。 如果将具有 aID 属性的 POJO 实例作为 ID 值写入云数据存储,则 Spring 数据云数据存储将从云数据存储获取新分配的 ID 值,并在 POJO 中设置该值以进行保存。 由于基元 ID 属性不能默认为,因此不会分配键。Long
null
long
null
0
14.2.4. 字段
POJO 上的所有可访问属性都会自动识别为云数据存储字段。 字段命名由 thebean 上默认定义。 注释可以选择提供与属性不同的字段名称。PropertyNameFieldNamingStrategy
DatastoreMappingContext
@Field
14.2.5. 支持的类型
Spring 数据云数据存储支持以下类型的常规字段和集合元素:
类型 | 存储为 |
| com.google.cloud.datastore.TimestampValue |
| com.google.cloud.datastore.BlobValue |
| com.google.cloud.datastore.LatLngValue |
| com.google.cloud.datastore.BooleanValue |
| com.google.cloud.datastore.DoubleValue |
| com.google.cloud.datastore.longvalue |
| com.google.cloud.datastore.longvalue |
| com.google.cloud.datastore.StringValue |
| com.google.cloud.datastore.EntityValue |
| com.google.cloud.datastore.KeyValue |
| com.google.cloud.datastore.BlobValue |
爪哇值 | com.google.cloud.datastore.StringValue |
此外,还支持可转换为表中列出的所有类型。org.springframework.core.convert.support.DefaultConversionService
14.2.6. 自定义类型
自定义转换器可用于扩展对用户定义类型的类型支持。
- 转换器需要在两个方向上实现接口。
org.springframework.core.convert.converter.Converter
- 用户定义的类型需要映射到云数据存储支持的基本类型之一。
- 两个转换器(读取和写入)的实例需要传递给构造函数,然后必须将其作为 afor 提供。
DatastoreCustomConversions
@Bean
DatastoreCustomConversions
例如:
我们希望在我们的POJO上有一个type的字段,并希望将其存储为字符串属性:Album
Singer
@Entity
public class Singer {
@Id
String singerId;
String name;
Album album;
}
其中相册是一个简单的类:
public class Album {
String albumName;
LocalDate date;
}
我们必须定义两个转换器:
//Converter to write custom Album type
static final Converter<Album, String> ALBUM_STRING_CONVERTER =
new Converter<Album, String>() {
@Override
public String convert(Album album) {
return album.getAlbumName() + " " + album.getDate().format(DateTimeFormatter.ISO_DATE);
}
};
//Converters to read custom Album type
static final Converter<String, Album> STRING_ALBUM_CONVERTER =
new Converter<String, Album>() {
@Override
public Album convert(String s) {
String[] parts = s.split(" ");
return new Album(parts[0], LocalDate.parse(parts[parts.length - 1], DateTimeFormatter.ISO_DATE));
}
};
这将在我们的文件中配置:@Configuration
@Configuration
public class ConverterConfiguration {
@Bean
public DatastoreCustomConversions datastoreCustomConversions() {
return new DatastoreCustomConversions(
Arrays.asList(
ALBUM_STRING_CONVERTER,
STRING_ALBUM_CONVERTER));
}
}
14.2.7. 集合和数组
支持受支持类型的数组和集合(实现的类型)。 它们存储为。 元素将转换为云数据存储支持的类型,individually.is 出现异常,元素将转换为。java.util.Collection
com.google.cloud.datastore.ListValue
byte[]
com.google.cloud.datastore.Blob
14.2.8. 集合的自定义转换器
用户可以提供从自定义集合类型的转换器。 只需要读取转换器,在写入端使用集合 API 将集合转换为内部列表类型。List<?>
集合转换器需要实现接口。org.springframework.core.convert.converter.Converter
例:
让我们从前面的示例改进 Singer 类。 我们想要一个类型的字段,而不是类型的字段:Album
Set<Album>
@Entity
public class Singer {
@Id
String singerId;
String name;
Set<Album> albums;
}
我们只需要定义一个读转换器:
static final Converter<List<?>, Set<?>> LIST_SET_CONVERTER =
new Converter<List<?>, Set<?>>() {
@Override
public Set<?> convert(List<?> source) {
return Collections.unmodifiableSet(new HashSet<>(source));
}
};
并将其添加到自定义转换器列表中:
@Configuration
public class ConverterConfiguration {
@Bean
public DatastoreCustomConversions datastoreCustomConversions() {
return new DatastoreCustomConversions(
Arrays.asList(
LIST_SET_CONVERTER,
ALBUM_STRING_CONVERTER,
STRING_ALBUM_CONVERTER));
}
}
14.2.9. 继承层次结构
通过继承相关的 Java 实体类型可以存储在同一种类中。 当读取和查询使用或超类作为类型参数的实体时,如果用以下方法注释超类及其子类,则可以接收子类的实例:DatastoreRepository
DatastoreTemplate
DiscriminatorField
DiscriminatorValue
@Entity(name = "pets")
@DiscriminatorField(field = "pet_type")
abstract class Pet {
@Id
Long id;
abstract String speak();
}
@DiscriminatorValue("cat")
class Cat extends Pet {
@Override
String speak() {
return "meow";
}
}
@DiscriminatorValue("dog")
class Dog extends Pet {
@Override
String speak() {
return "woof";
}
}
@DiscriminatorValue("pug")
class Pug extends Dog {
@Override
String speak() {
return "woof woof";
}
}
所有 3 种类型的实例都存储在 Kind 中。 由于使用了单个 Kind,因此层次结构中的所有类都必须共享相同的 ID 属性,并且层次结构中任何类型的任何实例都不能共享相同的 ID 值。pets
云数据存储中的实体行将其各自的类型存储在根超类指定的字段中(在本例中)。 使用给定类型参数的读取和查询将匹配每个实体及其特定类型。 例如,读取 a将生成一个包含所有 3 种类型的实例的列表。 但是,读取 a 将生成一个仅包含和实例的列表。 您可以在 Java 实体中包含歧视字段,但其类型必须可转换为集合或数组。 在区分字段中设置的任何值都将在写入云数据存储时被覆盖。DiscriminatorValue
DiscriminatorField
pet_type
List<Pet>
List<Dog>
Dog
Pug
pet_type
String
14.3. 关系
有三种方法可以表示本节中介绍的实体之间的关系:
- 直接存储在包含实体字段中的嵌入实体
-
@Descendant
一对多关系的批注属性 -
@Reference
没有层次结构的一般关系的批注属性 -
@LazyReference
类似于,但在访问属性时实体是延迟加载的。 (请注意,加载父实体时会检索子项的键。@Reference
14.3.1. 嵌入实体
类型也带有批注的字段将转换为父实体并存储在父实体中。@Entity
EntityValue
下面是包含 JSON 格式的嵌入式实体的云数据存储实体的示例:
{
"name" : "Alexander",
"age" : 47,
"child" : {"name" : "Philip" }
}
这对应于一对简单的 Java 实体:
import com.google.cloud.spring.data.datastore.core.mapping.Entity;
import org.springframework.data.annotation.Id;
@Entity("parents")
public class Parent {
@Id
String name;
Child child;
}
@Entity
public class Child {
String name;
}
Child
实体不存储在自己的种类中。 它们被完整地存储在同类的领域。child
parents
支持多个级别的嵌入实体。
嵌入实体不需要 havefield,只有顶级实体才需要。 |
例:
实体可以保存其自己的类型的嵌入实体。 我们可以使用此功能将树存储在云数据存储中:
import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
import com.google.cloud.spring.data.datastore.core.mapping.Entity;
import org.springframework.data.annotation.Id;
@Entity
public class EmbeddableTreeNode {
@Id
long value;
EmbeddableTreeNode left;
EmbeddableTreeNode right;
Map<String, Long> longValues;
Map<String, List<Timestamp>> listTimestamps;
public EmbeddableTreeNode(long value, EmbeddableTreeNode left, EmbeddableTreeNode right) {
this.value = value;
this.left = left;
this.right = right;
}
}
地图
地图将存储为嵌入实体,其中键值将成为嵌入实体中的字段名称。 这些映射中的值类型可以是任何常规支持的属性类型,并且键值将使用配置的转换器转换为 String。
此外,可以嵌入实体集合;它将被转换为卡通写入。ListValue
例:
我们想要存储一个通用树,而不是上一个示例中的二叉树 (每个节点可以有任意数量的子节点)在云数据存储中。 为此,我们需要创建一个类型字段:List<EmbeddableTreeNode>
import com.google.cloud.spring.data.datastore.core.mapping.Embedded;
import org.springframework.data.annotation.Id;
public class EmbeddableTreeNode {
@Id
long value;
List<EmbeddableTreeNode> children;
Map<String, EmbeddableTreeNode> siblingNodes;
Map<String, Set<EmbeddableTreeNode>> subNodeGroups;
public EmbeddableTreeNode(List<EmbeddableTreeNode> children) {
this.children = children;
}
}
由于地图存储为实体,因此它们可以进一步保存嵌入实体:
- 单个嵌入对象中的值可以存储在嵌入地图的值中。
- 值中嵌入对象的集合也可以存储为嵌入映射的值。
- 值中的映射进一步存储为嵌入实体,并对其值递归应用相同的规则。
14.3.2. 祖先-后代关系
父子关系通过注释得到支持。@Descendants
与嵌入的子代不同,后代是完全形成的实体,它们以自己的种类存在。 父实体没有额外的字段来保存后代实体。 相反,该关系在后代的键中捕获,这些键引用其父实体:
import com.google.cloud.spring.data.datastore.core.mapping.Descendants;
import com.google.cloud.spring.data.datastore.core.mapping.Entity;
import org.springframework.data.annotation.Id;
@Entity("orders")
public class ShoppingOrder {
@Id
long id;
@Descendants
List<Item> items;
}
@Entity("purchased_item")
public class Item {
@Id
Key purchasedItemKey;
String name;
Timestamp timeAddedToOrder;
}
例如,GQL 键文本表示形式的实例也将包含父 ID 值:Item
ShoppingOrder
Key(orders, '12345', purchased_item, 'eggs')
父级的 GQL 键文本表示形式为:ShoppingOrder
Key(orders, '12345')
云数据存储实体以自己的类型单独存在。
这:ShoppingOrder
{
"id" : 12345
}
该订单中的两个项目:
{
"purchasedItemKey" : Key(orders, '12345', purchased_item, 'eggs'),
"name" : "eggs",
"timeAddedToOrder" : "2014-09-27 12:30:00.45-8:00"
}
{
"purchasedItemKey" : Key(orders, '12345', purchased_item, 'sausage'),
"name" : "sausage",
"timeAddedToOrder" : "2014-09-28 11:30:00.45-9:00"
}
对象的父子关系结构使用数据存储的祖先关系存储在云数据存储中。 由于关系由 Ancestor 机制定义,因此父实体或子实体中不需要额外的列来存储此关系。 关系链接是后代实体的键值的一部分。 这些关系可以有很多层次。
保存子实体的属性必须是类似集合的,但它们可以是任何受支持的可相互转换的类似集合的类型,这些类型支持常规属性(如数组等)。 子项必须具有其 ID 类型,因为云数据存储将祖先关系链接存储在子项的键中。List
Set
Key
读取或保存实体会自动导致分别读取或保存该实体下的所有后续级别。 如果创建新子项并将其添加到带批注的属性中,并且键属性保留为 null,则将为该子项分配一个新键。 检索到的子项的顺序可能与保存的原始属性中的顺序不同。@Descendants
子实体不能从一个父实体的属性移动到另一个父实体的属性,除非子实体的键属性设置为包含新父级作为祖先的值。 由于云数据存储实体键可以有多个父实体,因此子实体可能会出现在多个父实体的属性中。 由于实体键在 Cloud Datastore 中是不可变的,因此要更改子项的键,必须删除现有项密钥,然后使用新键重新保存。null
14.3.3. 关键引用关系
一般关系可以使用注释来存储。@Reference
import org.springframework.data.annotation.Reference;
import org.springframework.data.annotation.Id;
@Entity
public class ShoppingOrder {
@Id
long id;
@Reference
List<Item> items;
@Reference
Item specialSingleItem;
}
@Entity
public class Item {
@Id
Key purchasedItemKey;
String name;
Timestamp timeAddedToOrder;
}
@Reference
关系是居住在自己种类中的完全形成的实体之间的关系。 实体之间的关系存储在其中的键字段,这些字段由Spring Data Cloud Datastore解析为底层Java实体类型:ShoppingOrder
Item
ShoppingOrder
{
"id" : 12345,
"specialSingleItem" : Key(item, "milk"),
"items" : [ Key(item, "eggs"), Key(item, "sausage") ]
}
引用属性可以是单数的,也可以是类似集合的。 这些属性对应于实体和云数据存储类型中保存所引用实体的键值的实际列。 引用的实体是其他类型的成熟实体。
与关系类似,读取或写入实体将递归读取或写入所有级别的所有引用实体。 如果引用的实体具有 ID 值,则它们将另存为新实体,并将具有由云数据存储分配的 ID 值。 实体的键与实体作为引用持有的键之间的关系没有要求。 从云数据存储读回时,不会保留类似集合的引用属性的顺序。@Descendants
null
14.4. 数据存储操作和模板
DatastoreOperations
及其实现提供了 Spring 开发人员熟悉的模板模式。DatastoreTemplate
使用适用于数据存储的 Spring 引导启动器提供的自动配置,Spring 应用程序上下文将包含一个完全配置的对象,您可以在应用程序中自动连线该对象:DatastoreTemplate
@SpringBootApplication
public class DatastoreTemplateExample {
@Autowired
DatastoreTemplate datastoreTemplate;
public void doSomething() {
this.datastoreTemplate.deleteAll(Trader.class);
//...
Trader t = new Trader();
//...
this.datastoreTemplate.save(t);
//...
List<Trader> traders = datastoreTemplate.findAll(Trader.class);
//...
}
}
模板 API 为以下各项提供了方便的方法:
- 写入操作(保存和删除)
- 读写事务
14.4.1. GQL 查询
除了按实体的 ID 检索实体外,您还可以提交查询。
<T> Iterable<T> query(Query<? extends BaseEntity> query, Class<T> entityClass);
<A, T> Iterable<T> query(Query<A> query, Function<A, T> entityFunc);
Iterable<Key> queryKeys(Query<Key> query);
这些方法分别允许查询:
- 给定实体类使用所有相同的映射和转换功能映射的实体
- 给定映射函数生成的任意类型
- 仅查询找到的实体的云数据存储键
14.4.2. 按 ID 查找
使用您可以按 id 查找实体。例如:DatastoreTemplate
Trader trader = this.datastoreTemplate.findById("trader1", Trader.class);
List<Trader> traders = this.datastoreTemplate.findAllById(Arrays.asList("trader1", "trader2"), Trader.class);
List<Trader> allTraders = this.datastoreTemplate.findAll(Trader.class);
云数据存储使用具有强一致性的基于密钥的读取,但使用具有最终一致性的查询。 在上面的示例中,前两个读取利用键,而第三个读取使用基于相应 Kind of 的查询运行。Trader
指标
默认情况下,所有字段都已编制索引。 要禁用特定字段的索引,可以使用注释。@Unindexed
例:
import com.google.cloud.spring.data.datastore.core.mapping.Unindexed;
public class ExampleItem {
long indexedField;
@Unindexed
long unindexedField;
@Unindexed
List<String> unindexedListField;
}
直接或通过查询方法使用查询时,如果 select 语句不是,则云数据存储需要复合自定义索引,或者如果子句中有多个筛选条件,则需要复合自定义索引。SELECT *
WHERE
使用偏移量、限制和排序进行读取
DatastoreRepository
自定义的实体存储库实现了 Spring 数据,它支持使用页码和页面大小的偏移和限制。 通过提供 ATO,也支持分页和排序选项。PagingAndSortingRepository
DatastoreTemplate
DatastoreQueryOptions
findAll
部分读取
尚不支持此功能。
14.4.3. 写入/更新
的写入方法接受 POJO 并将其所有属性写入数据存储。 所需的数据存储类型和实体元数据是从给定对象的实际类型中获取的。DatastoreOperations
如果从数据存储中检索了 POJO,并且更改了其 ID 值,然后写入或更新了该操作,则操作将像针对具有新 ID 值的行一样发生。 具有原始 ID 值的实体不会受到影响。
Trader t = new Trader();
this.datastoreTemplate.save(t);
该方法的行为类似于更新或插入。save
部分更新
尚不支持此功能。
14.4.4. 交易
读取和写入事务通过以下方式提供:DatastoreOperations
performTransaction
@Autowired
DatastoreOperations myDatastoreOperations;
public String doWorkInsideTransaction() {
return myDatastoreOperations.performTransaction(
transactionDatastoreOperations -> {
// Work with transactionDatastoreOperations here.
// It is also a DatastoreOperations object.
return "transaction completed";
}
);
}
该方法接受a,它提供了一个对象的实例。 函数的最终返回值和类型由用户确定。 您可以像使用常规对象一样使用此对象,但有一个例外:performTransaction
Function
DatastoreOperations
DatastoreOperations
- 它不能执行子事务。
由于云数据存储的一致性保证,事务中使用的实体之间的操作和关系存在限制。
带有@Transactional注释的声明性事务
此功能需要 bean,这是在使用时提供的。DatastoreTransactionManager
spring-cloud-gcp-starter-data-datastore
DatastoreTemplate
并支持将注释作为事务运行方法。 如果一个方法用调用另一个也带注释的方法,则这两个方法将在同一事务中工作。不能在注释方法中使用,因为 Cloud Datastore 不支持事务中的事务。DatastoreRepository
@Transactional
@Transactional
performTransaction
@Transactional
14.4.5. 地图的读写支持
您可以使用类型的地图,而不是实体对象,方法是直接在云数据存储中读取和写入实体对象。Map<String, ?>
这与使用包含 Map 属性的实体对象的情况不同。 |
映射键用作数据存储实体的字段名称,映射值将转换为数据存储支持的类型。 仅支持简单类型(即不支持集合)。 可以添加自定义值类型的转换器(请参阅自定义类型部分)。
例:
Map<String, Long> map = new HashMap<>();
map.put("field1", 1L);
map.put("field2", 2L);
map.put("field3", 3L);
keyForMap = datastoreTemplate.createKey("kindName", "id");
//write a map
datastoreTemplate.writeMap(keyForMap, map);
//read a map
Map<String, Long> loadedMap = datastoreTemplate.findByIdAsMap(keyForMap, Long.class);
14.5. 仓库
Spring 数据存储库是一个抽象,可以减少样板代码。
例如:
public interface TraderRepository extends DatastoreRepository<Trader, String> {
}
Spring Data 生成指定接口的工作实现,可以自动连接到应用程序中。
类型参数引用基础域类型。 在这种情况下,第二个类型参数是指域类型的键的类型。Trader
DatastoreRepository
String
public class MyApplication {
@Autowired
TraderRepository traderRepository;
public void demo() {
this.traderRepository.deleteAll();
String traderId = "demo_trader";
Trader t = new Trader();
t.traderId = traderId;
this.tradeRepository.save(t);
Iterable<Trader> allTraders = this.traderRepository.findAll();
int count = this.traderRepository.count();
}
}
存储库允许您定义自定义查询方法(详见以下部分),以便根据过滤和分页参数进行检索、计数和删除。 筛选参数可以是已配置的自定义转换器支持的类型。
14.5.1. 按约定查询方法
public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
List<Trader> findByAction(String action);
//throws an exception if no results
Trader findOneByAction(String action);
//because of the annotation, returns null if no results
@Nullable
Trader getByAction(String action);
Optional<Trader> getOneByAction(String action);
int countByAction(String action);
boolean existsByAction(String action);
List<Trade> findTop3ByActionAndSymbolAndPriceGreaterThanAndPriceLessThanOrEqualOrderBySymbolDesc(
String action, String symbol, double priceFloor, double priceCeiling);
Page<TestEntity> findByAction(String action, Pageable pageable);
Slice<TestEntity> findBySymbol(String symbol, Pageable pageable);
List<TestEntity> findBySymbol(String symbol, Sort sort);
Stream<TestEntity> findBySymbol(String symbol);
}
在上面的示例中,查询方法是根据使用Spring 数据查询创建命名约定的方法名称生成的。TradeRepository
您可以使用Spring Data JPA 属性表达式引用嵌套字段 |
云数据存储仅支持通过 AND 联接的筛选器组件,以及以下操作:
-
equals
-
greater than or equals
-
greater than
-
less than or equals
-
less than
-
is null
编写仅指定这些方法的签名的自定义存储库接口后,将为您生成实现,并可与存储库的自动连线实例一起使用。 由于云数据存储要求显式选择的字段必须全部一起出现在复合索引中,因此基于名称的查询方法将运行为。find
SELECT *
还支持删除查询。 例如,查询方法如 或删除 找到的实体。 删除查询作为单独的读取和删除操作运行,而不是作为单个事务运行,因为除非指定了查询的祖先,否则 Cloud Data Store 无法在事务中进行查询。 因此,和名称约定查询方法不能通过任一注释在事务中使用。deleteByAction
removeByAction
findByAction
removeBy
deleteBy
performInTransaction
@Transactional
删除查询可以具有以下返回类型:
- 一个整数类型,表示删除的实体数
- 已删除的实体的集合
- “虚空”
方法可以具有参数来控制分页和排序,或者参数来控制排序。 有关详细信息,请参阅Spring 数据文档。org.springframework.data.domain.Pageable
org.springframework.data.domain.Sort
为了在存储库方法中返回多个项目,我们支持 Java 集合以及 and。 如果方法的返回类型为 ,则返回的对象将包括当前页、结果总数和总页数。org.springframework.data.domain.Page
org.springframework.data.domain.Slice
org.springframework.data.domain.Page
返回的方法运行其他查询以计算总页数。 另一方面,返回的方法不运行任何其他查询,因此效率更高。 |
14.5.2. 仓库方法中的空结果处理
Java 可用于指示可能缺少返回值。java.util.Optional
或者,查询方法可以在没有包装器的情况下返回结果。 在这种情况下,通过返回来指示缺少查询结果。 存储库方法保证永远不会返回集合,而是返回相应的空集合。null
null
您可以启用可空性检查。有关更多详细信息,请参阅Spring 框架的可空性文档。 |
14.5.3. 按示例查询
按示例查询是一种替代查询技术。 它支持基于用户提供的对象生成动态查询。有关详细信息,请参阅Spring 数据文档。
不支持的功能:
- 目前仅支持相等查询(无忽略大小写匹配、正则表达式匹配等)。
- 不支持每字段匹配器。
- 不支持嵌入实体匹配。
- 不支持投影。
例如,如果要查找姓氏为“Smith”的所有用户,则可以使用以下代码:
userRepository.findAll(
Example.of(new User(null, null, "Smith"))
null
fields are not used in the filter by default. If you want to include them, you would use the following code:
userRepository.findAll(
Example.of(new User(null, null, "Smith"), ExampleMatcher.matching().withIncludeNullValues())
您还可以扩展最初由 FluentQuery 链接样式中的示例定义的查询规范:
userRepository.findBy(
Example.of(new User(null, null, "Smith")), q -> q.sortBy(Sort.by("firstName")).firstValue());
userRepository.findBy(
Example.of(new User(null, null, "Smith")), FetchableFluentQuery::stream);
14.5.4. 自定义 GQL 查询方法
可以通过以下两种方式之一将自定义 GQL 查询映射到存储库方法:
-
namedQueries
属性文件 - 使用注释
@Query
带批注的查询方法
使用注释:@Query
GQL 的标记名称对应于方法参数的注释名称。@Param
public interface TraderRepository extends DatastoreRepository<Trader, String> {
@Query("SELECT * FROM traders WHERE name = @trader_name")
List<Trader> tradersByName(@Param("trader_name") String traderName);
@Query("SELECT * FROM traders WHERE name = @trader_name")
Stream<Trader> tradersStreamByName(@Param("trader_name") String traderName);
@Query("SELECT * FROM test_entities_ci WHERE name = @trader_name")
TestEntity getOneTestEntity(@Param("trader_name") String traderName);
@Query("SELECT * FROM traders WHERE name = @trader_name")
List<Trader> tradersByNameSort(@Param("trader_name") String traderName, Sort sort);
@Query("SELECT * FROM traders WHERE name = @trader_name")
Slice<Trader> tradersByNameSlice(@Param("trader_name") String traderName, Pageable pageable);
@Query("SELECT * FROM traders WHERE name = @trader_name")
Page<Trader> tradersByNamePage(@Param("trader_name") String traderName, Pageable pageable);
}
当返回类型 isor 时,指向页面之后的位置的结果集游标将保留在返回的对象中。若要利用光标查询下一页或切片,请使用。Slice
Pageable
Slice
Page
result.getPageable().next()
|
Slice<Trader> slice1 = tradersByNamePage("Dave", PageRequest.of(0, 5));
Slice<Trader> slice2 = tradersByNamePage("Dave", slice1.getPageable().next());
不能在类型参数是另一个类的子类的存储库中使用这些查询方法 注释。 |
支持以下参数类型:
-
com.google.cloud.Timestamp
-
com.google.cloud.datastore.Blob
-
com.google.cloud.datastore.Key
-
com.google.cloud.datastore.Cursor
-
java.lang.Boolean
-
java.lang.Double
-
java.lang.Long
-
java.lang.String
-
enum
值。 这些是查询的值。String
除此以外,还支持每种类型的数组形式。Cursor
如果要获取查询的项计数,或者查询返回任何项,请分别设置注释的理论属性。 在这些情况下,查询方法的返回类型应为整数类型或布尔类型。count = true
exists = true
@Query
云数据存储为检索每一行的各种类型提供特殊列。 选择此特殊列特别有用且高效。SELECT __key__ FROM …
Key
__key__
count
exists
您还可以查询非实体类型:
@Query(value = "SELECT __key__ from test_entities_ci")
List<Key> getKeys();
@Query(value = "SELECT __key__ from test_entities_ci limit 1")
Key getKey();
要在自定义查询中使用带批注的字段,请使用关键字作为字段名称。参数类型应为 of,如以下示例所示。@Id
__key__
Key
存储库方法:
@Query("select * from test_entities_ci where size = @size and __key__ = @id")
LinkedList<TestEntity> findEntities(@Param("size") long size, @Param("id") Key id);
使用 using 方法从 id 值生成密钥,并将其用作存储库方法的参数:DatastoreTemplate.createKey
this.testEntityRepository.findEntities(1L, datastoreTemplate.createKey(TestEntity.class, 1L))
SpEL 可用于提供 GQL 参数:
@Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act
AND price > :#{#priceRadius * -1} AND price < :#{#priceRadius * 2}")
List<Trade> fetchByActionNamedQuery(@Param("act") String action, @Param("priceRadius") Double r);
种类名称可以直接写在 GQL 注释中。 种类名称也可以从域类的注释中解析。@Entity
在这种情况下,查询应引用具有完全限定的类名的表名,这些类名被字符括起来:。 当 SpEL 表达式出现在提供给注释的种类名称中时,这很有用。 例如:|
|fully.qualified.ClassName|
@Entity
@Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act")
List<Trade> fetchByActionNamedQuery(@Param("act") String action);
具有命名查询属性的查询方法
您还可以在属性文件中使用云数据存储参数标记和 SpEL 表达式指定查询。
默认情况下,属性指向文件。 可以通过提供 GQL 作为“interface.method”属性的值,在属性文件中指定方法的查询:namedQueriesLocation
@EnableDatastoreRepositories
META-INF/datastore-named-queries.properties
不能在类型参数是另一个类的子类的存储库中使用这些查询方法 注释。 |
Trader.fetchByName=SELECT * FROM traders WHERE name = @tag0
public interface TraderRepository extends DatastoreRepository<Trader, String> {
// This method uses the query from the properties file instead of one generated based on name.
List<Trader> fetchByName(@Param("tag0") String traderName);
}
14.5.5. 交易
这些事务的工作方式与存储库的事务非常相似,但特定于存储库的域类型,并提供存储库功能而不是模板功能。DatastoreOperations
例如,这是一个读写事务:
@Autowired
DatastoreRepository myRepo;
public String doWorkInsideTransaction() {
return myRepo.performTransaction(
transactionDatastoreRepo -> {
// Work with the single-transaction transactionDatastoreRepo here.
// This is a DatastoreRepository object.
return "transaction completed";
}
);
}
14.5.6. 预测
Spring Data Cloud Datastore支持投影。 您可以根据域类型定义投影接口,并添加在存储库中返回它们的查询方法:
public interface TradeProjection {
String getAction();
@Value("#{target.symbol + ' ' + target.action}")
String getSymbolAndAction();
}
public interface TradeRepository extends DatastoreRepository<Trade, Key> {
List<Trade> findByTraderId(String traderId);
List<TradeProjection> findByAction(String action);
@Query("SELECT action, symbol FROM trades WHERE action = @action")
List<TradeProjection> findByQuery(String action);
}
投影可以由基于名称约定的查询方法以及自定义 GQL 查询提供。 如果使用自定义 GQL 查询,则可以进一步将从云数据存储检索的字段限制为投影所需的字段。 但是,自定义选择语句(不使用的语句)需要包含所选字段的复合索引。SELECT *
使用 SpEL 定义的投影类型的属性使用基础域对象的固定名称。 因此,访问基础属性采用以下形式。target
target.<property-name>
14.5.7. REST 仓库
使用 Spring 引导运行时,只需将此依赖项添加到您的 pom 文件中,就可以将存储库作为 REST 服务公开:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
如果您希望配置参数(例如路径),则可以使用注释:@RepositoryRestResource
@RepositoryRestResource(collectionResourceRel = "trades", path = "trades")
public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
}
例如,您可以使用或任何特定的交易方式检索存储库中的所有对象。Trade
curl http://<server>:<port>/trades
curl http://<server>:<port>/trades/<trader_id>
您还可以使用文件保存对象的 JSON 表示形式来编写交易。curl -XPOST -H"Content-Type: application/json" [email protected] http://<server>:<port>/trades/
test.json
Trade
要删除交易,您可以使用curl -XDELETE http://<server>:<port>/trades/<trader_id>
14.6. 事件
Spring 数据云数据存储发布事件,将 Spring 框架扩展到您注册的 bean 可以接收的上下文。ApplicationEvent
ApplicationListener
类型 | 描述 | 内容 |
| 在按键读取操作运行后立即发布 | 从云数据存储读取的实体和请求中的原始密钥。 |
| 在读取查询操作运行后立即发布 | 从云数据存储读取的实体和请求中的原始查询。 |
| 在运行保存操作之前立即发布 | 要发送到云数据存储的实体和要保存的原始 Java 对象。 |
| 在运行保存操作后立即发布 | 发送到云数据存储的实体和要保存的原始 Java 对象。 |
| 在运行删除操作之前立即发布 | 要发送到云数据存储的密钥。最初为删除操作指定的目标实体、ID 值或实体类型。 |
| 在运行删除操作后立即发布 | 发送到云数据存储的密钥。最初为删除操作指定的目标实体、ID 值或实体类型。 |
14.7. 审计
Spring 数据云数据存储支持属性的注释和审核:@LastModifiedDate
@LastModifiedBy
@Entity
public class SimpleEntity {
@Id
String id;
@LastModifiedBy
String lastUser;
@LastModifiedDate
DateTime lastTouched;
}
插入、更新或保存后,框架将在生成数据存储实体并将其保存到云数据存储之前自动设置这些属性。
要利用这些功能,请将注释添加到配置类中,并为实现提供一个 Bean,其中类型是所需的属性类型,由以下人员注释:@EnableDatastoreAuditing
AuditorAware<A>
A
@LastModifiedBy
@Configuration
@EnableDatastoreAuditing
public class Config {
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of("YOUR_USERNAME_HERE");
}
}
该接口包含单个方法,该方法为批注的字段提供值,并且可以是任何类型。 一种替代方法是使用 Spring Security 的类型:AuditorAware
@LastModifiedBy
User
class SpringSecurityAuditorAware implements AuditorAware<User> {
public Optional<User> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}
您还可以通过提供 Bean forand 提供 Bean 名称来为批注的属性设置自定义提供程序。@LastModifiedDate
DateTimeProvider
@EnableDatastoreAuditing(dateTimeProviderRef = "customDateTimeProviderBean")
14.8. Partitioning Data by Namespace
You can partition your data by using more than one namespace. This is the recommended method for multi-tenancy in Cloud Datastore.
@Bean
public DatastoreNamespaceProvider namespaceProvider() {
// return custom Supplier of a namespace string.
}
这是同义词。 通过提供此 Bean 的定制实现(例如,提供线程本地命名空间名称),可以指示应用程序使用多个命名空间。 您执行的每个读取、写入、查询和事务都将使用此供应商提供的命名空间。DatastoreNamespaceProvider
Supplier<String>
请注意,如果您定义命名空间提供程序 bean,则您提供的命名空间 in 将被忽略。application.properties
14.9. 弹簧启动执行器支持
14.9.1. 云数据存储运行状况指示器
如果您使用的是 Spring 引导执行器,则可以利用调用的云数据存储运行状况指示器。 运行状况指示器将验证云数据存储是否已启动并可供应用程序访问。 要启用它,您需要做的就是将Spring 启动执行器添加到您的项目中。datastore
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
14.10. 示例
提供了一个简单的Spring 引导应用程序和更高级的示例 Spring引导应用程序,以展示如何使用 Spring 数据云数据存储启动器和模板。
14.11. 测试
Testcontainers
提供模块。在文档中查看更多信息gcloud
DatastoreEmulatorContainer
15. 弹簧数据云还原
目前不支持某些功能:按示例查询、投影和审核。 |
Spring 数据是用于在众多存储技术中存储和检索 POJO 的抽象。 Spring Cloud GCP 在纯模式下为Google Cloud Firestore添加了 Spring 数据反应式存储库支持,提供响应式模板和存储库支持。 要开始使用此库,请将 theartifact 添加到您的项目中。spring-cloud-gcp-data-firestore
Maven 仅使用此模块的坐标,使用Spring Cloud GCP BOM:
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-data-firestore</artifactId>
</dependency>
格拉德尔坐标:
dependencies {
implementation("com.google.cloud:spring-cloud-gcp-data-firestore")
}
我们为Spring Data Firestore提供了一个Spring Boot Starter,您可以使用我们推荐的自动配置设置。要使用启动器,请参阅下面的坐标。
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-data-firestore</artifactId>
</dependency>
格拉德尔坐标:
dependencies {
implementation("com.google.cloud:spring-cloud-gcp-starter-data-firestore")
}
15.1. 配置
15.1.1. 属性
Google Cloud Firestore 的 Spring Boot 启动器提供了以下配置选项:
名字 | 描述 | 必填 | 默认值 |
| 启用或禁用 Firestore 自动配置 | 不 | |
| 托管 Google Cloud Firestore API 的 GCP 项目 ID(如果与Spring Cloud GCP Core Module 中的 ID 不同) | 不 | |
| 启用模拟器的使用。如果设置为 true,则应将本地运行的模拟器实例的 host:port 设置为spring.cloud.gcp.firestore.host-port | 不 | |
| Firestore 服务的主机和端口;可以覆盖以指定连接到已在运行的Firestore 模拟器实例。 | 不 | |
| 用于使用 Google Cloud Firestore API 进行身份验证的 OAuth2 凭据(如果与Spring Cloud GCP Core Module 中的凭据不同) | 不 | |
| Base64编码的OAuth2凭据,用于使用Google Cloud Firestore API进行身份验证,如果与Spring Cloud GCP核心模块中的凭据不同 | 不 | |
| Spring Cloud GCP Cloud Firestore 凭据的 OAuth2 范围 | 不 | www.googleapis.com/auth/datastore |
15.1.2. 支持的类型
在定义持久实体或绑定查询参数时,可以使用以下字段类型:
-
Long
-
Integer
-
Double
-
Float
-
String
-
Boolean
-
Character
-
Date
-
Map
-
List
-
Enum
-
com.google.cloud.Timestamp
-
com.google.cloud.firestore.GeoPoint
-
com.google.cloud.firestore.Blob
15.1.3. 反应式存储库设置
Spring 数据存储库可以通过主类上的注释进行配置。 使用我们的Spring Boot Starter for Spring Data Cloud Firestore,会自动添加。 不需要将其添加到任何其他类中,除非需要覆盖 @EnableReactiveFirestoreRepositories 提供的更细粒度的配置参数。@EnableReactiveFirestoreRepositories
@Configuration
@EnableReactiveFirestoreRepositories
15.1.4. 自动配置
我们的 Spring 引导自动配置在 Spring 应用程序上下文中创建以下可用的 bean:
- 的实例
FirestoreTemplate
- 启用存储库时扩展的所有用户定义的存储库的实例(具有其他 Cloud Firestore 功能的扩展)
FirestoreReactiveRepository
ReactiveCrudRepository
- 来自Firestore的Google Cloud Java客户端的Firestore实例,以方便和较低级别的API访问
15.2. 对象映射
Spring Data Cloud Firestore 允许您通过注释将域 POJO 映射到Cloud Firestore 集合和文档:
import com.google.cloud.firestore.annotation.DocumentId;
import com.google.cloud.spring.data.firestore.Document;
@Document(collectionName = "usersCollection")
public class User {
/** Used to test @PropertyName annotation on a field. */
@PropertyName("drink")
public String favoriteDrink;
@DocumentId private String name;
private Integer age;
public User() {}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return this.age;
}
public void setAge(Integer age) {
this.age = age;
}
}
@Document(collectionName = "usersCollection")
批注配置此类型文档的集合名称。 此注释是可选的,默认情况下集合名称派生自类名。
@DocumentId
批注标记要用作文档 ID 的字段。 此注释是必需的,并且注释字段只能为类型。String
如果属性带有批注,则在保存实体时自动生成文档 ID。 |
在内部,我们使用 Firestore 客户端库对象映射。有关支持的注释,请参阅文档。 |
15.2.1. 嵌入实体和列表
Spring Data Cloud Firestore 支持自定义类型和列表的嵌入属性。 给定自定义 POJO 定义,可以在实体中具有此类型的属性或此类型的列表。 它们作为嵌入式文档(或数组,相应地)存储在Cloud Firestore中。
例:
@Document(collectionName = "usersCollection")
public class User {
/** Used to test @PropertyName annotation on a field. */
@PropertyName("drink")
public String favoriteDrink;
@DocumentId private String name;
private Integer age;
private List<String> pets;
private List<Address> addresses;
private Address homeAddress;
public List<String> getPets() {
return this.pets;
}
public void setPets(List<String> pets) {
this.pets = pets;
}
public List<Address> getAddresses() {
return this.addresses;
}
public void setAddresses(List<Address> addresses) {
this.addresses = addresses;
}
public Timestamp getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Timestamp updateTime) {
this.updateTime = updateTime;
}
@PropertyName("address")
public Address getHomeAddress() {
return this.homeAddress;
}
@PropertyName("address")
public void setHomeAddress(Address homeAddress) {
this.homeAddress = homeAddress;
}
public static class Address {
String streetAddress;
String country;
public Address() {}
}
}
15.3. 反应式存储库
Spring 数据存储库是一个抽象,可以减少样板代码。
例如:
public interface UserRepository extends FirestoreReactiveRepository<User> {
Flux<User> findBy(Pageable pageable);
Flux<User> findByAge(Integer age);
Flux<User> findByAge(Integer age, Sort sort);
Flux<User> findByAgeOrderByNameDesc(Integer age);
Flux<User> findAllByOrderByAge();
Flux<User> findByAgeNot(Integer age);
Flux<User> findByNameAndAge(String name, Integer age);
Flux<User> findByHomeAddressCountry(String country);
Flux<User> findByFavoriteDrink(String drink);
Flux<User> findByAgeGreaterThanAndAgeLessThan(Integer age1, Integer age2);
Flux<User> findByAgeGreaterThan(Integer age);
Flux<User> findByAgeGreaterThan(Integer age, Sort sort);
Flux<User> findByAgeGreaterThan(Integer age, Pageable pageable);
Flux<User> findByAgeIn(List<Integer> ages);
Flux<User> findByAgeNotIn(List<Integer> ages);
Flux<User> findByAgeAndPetsContains(Integer age, List<String> pets);
Flux<User> findByNameAndPetsContains(String name, List<String> pets);
Flux<User> findByPetsContains(List<String> pets);
Flux<User> findByPetsContainsAndAgeIn(String pets, List<Integer> ages);
Mono<Long> countByAgeIsGreaterThan(Integer age);
}
Spring Data 生成指定接口的工作实现,可以自动连接到应用程序中。
类型参数引用基础域类型。User
FirestoreReactiveRepository
您可以使用Spring Data JPA 属性表达式引用嵌套字段 |
public class MyApplication {
@Autowired UserRepository userRepository;
void writeReadDeleteTest() {
List<User.Address> addresses =
Arrays.asList(
new User.Address("123 Alice st", "US"), new User.Address("1 Alice ave", "US"));
User.Address homeAddress = new User.Address("10 Alice blvd", "UK");
User alice = new User("Alice", 29, null, addresses, homeAddress);
User bob = new User("Bob", 60);
this.userRepository.save(alice).block();
this.userRepository.save(bob).block();
assertThat(this.userRepository.count().block()).isEqualTo(2);
assertThat(this.userRepository.findAll().map(User::getName).collectList().block())
.containsExactlyInAnyOrder("Alice", "Bob");
User aliceLoaded = this.userRepository.findById("Alice").block();
assertThat(aliceLoaded.getAddresses()).isEqualTo(addresses);
assertThat(aliceLoaded.getHomeAddress()).isEqualTo(homeAddress);
// cast to SimpleFirestoreReactiveRepository for method be reachable with Spring Boot 2.4
SimpleFirestoreReactiveRepository repository =
AopTestUtils.getTargetObject(this.userRepository);
StepVerifier.create(
repository
.deleteAllById(Arrays.asList("Alice", "Bob"))
.then(this.userRepository.count()))
.expectNext(0L)
.verifyComplete();
}
}
存储库允许您定义自定义查询方法(详见以下部分),以便根据筛选和分页参数进行检索和计数。
不支持带注释的自定义查询,因为 Cloud Firestore 中没有查询语言 |