160. Spring Data Cloud 数据存储

Spring Data是用于以多种存储技术存储和检索 POJO 的抽象。 Spring Cloud GCP 增加了对Google Cloud 数据存储的 Spring Data 支持。

Maven 仅使用Spring Cloud GCP BOM协调此模块:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-gcp-data-datastore</artifactId>
</dependency>

Gradle coordinates:

dependencies {
    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-data-datastore'
}

我们提供了一个用于 Spring Data Datastore 的 Spring Boot Starter,您可以使用它使用建议的自动配置设置。要使用启动器,请参见以下坐标。

Maven:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-gcp-starter-data-datastore</artifactId>
</dependency>

Gradle:

dependencies {
    compile group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-data-datastore'
}

此设置还负责引入 Cloud Java Cloud Datastore 库的最新兼容版本。

160.1 Configuration

要设置 Spring Data Cloud Datastore,您必须配置以下内容:

  • 设置与 Google Cloud Datastore 的连接详细信息。

160.1.1 Cloud Datastore 设置

您可以使用用于 Spring Data Datastore 的 Spring Boot Starter在 Spring 应用程序中自动配置 Google Cloud Datastore。它包含所有必要的设置,使您可以轻松地通过 Google Cloud 项目进行身份验证。提供以下配置选项:

NameDescriptionRequiredDefault value
spring.cloud.gcp.datastore.enabled启用 Cloud DatastoreClient 端Notrue
spring.cloud.gcp.datastore.project-id托管 Google Cloud Datastore API 的 GCP 项目 ID(如果与Spring Cloud GCP 核心模块中的 ID 不同)No
spring.cloud.gcp.datastore.credentials.location用于与 Google Cloud Datastore API 进行身份验证的 OAuth2 凭据(如果与Spring Cloud GCP 核心模块中的不同)No
spring.cloud.gcp.datastore.credentials.encoded-key用于与 Google Cloud Datastore API 进行身份验证的 Base64 编码的 OAuth2 凭据(如果与Spring Cloud GCP 核心模块中的不同)No
spring.cloud.gcp.datastore.credentials.scopesOAuth2 scope用于 Spring Cloud GCP Cloud 数据存储凭据Nohttps://www.googleapis.com/auth/datastore
spring.cloud.gcp.datastore.namespace要使用的 Cloud Datastore 命名空间NoGCP 项目中 Cloud Datastore 的默认名称空间

160.1.2 存储库设置

可以通过@Configuration主类上的@EnableDatastoreRepositoriesComments 配置 Spring Data Repository。使用我们用于 Spring Data Cloud Datastore 的 Spring Boot Starter,会自动添加@EnableDatastoreRepositories。不需要将其添加到任何其他类中,除非需要覆盖@EnableDatastoreRepositories提供的更精细的粒度配置参数。

160.1.3 Autoconfiguration

我们的 Spring Boot 自动配置会在 Spring 应用程序上下文中创建以下可用的 bean:

  • DatastoreTemplate的实例

  • 启用存储库后,扩展了CrudRepositoryPagingAndSortingRepositoryDatastoreRepository(具有其他 Cloud Datastore 功能的PagingAndSortingRepository的 extensions)的所有用户定义存储库的实例

  • 来自 Google Cloud JavaClient 端数据存储区的Datastore实例,以方便使用和较低级别的 API 访问

160.2 对象 Map

Spring Data Cloud Datastore 允许您通过 Comments 将域 POJOMap 到 Cloud Datastore 的种类和实体:

@Entity(name = "traders")
public class Trader {

	@Id
	@Field(name = "trader_id")
	String traderId;

	String firstName;

	String lastName;

	@Transient
	Double temporaryNumber;
}

Spring Data Cloud Datastore 将忽略带有@TransientComments 的任何属性。这些属性将不会写入或从 Cloud Datastore 读取。

160.2.1 Constructors

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

160.2.2 Kind

@Entity注解可以提供存储 Comments 类的实例的 Cloud Datastore 类型的名称,每行一个。

160.2.3 Keys

@Id标识与 ID 值相对应的属性。

您必须将 POJO 字段之一 Comments 为 ID 值,因为 Cloud Datastore 中的每个实体都需要一个 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 值。如果将具有Long ID 属性的 POJO 实例以null作为 ID 值写入 Cloud Datastore,则 Spring Data Cloud Datastore 将从 Cloud Datastore 获取新分配的 ID 值并将其设置在 POJO 中以进行保存。由于原始long ID 属性不能为null且默认为0,因此不会分配密钥。

160.2.4 Fields

POJO 上的所有可访问属性都将自动识别为 Cloud Datastore 字段。默认情况下,字段命名是由DatastoreMappingContext bean 上定义的PropertyNameFieldNamingStrategy生成的。 @FieldComments 可以选择提供与属性不同的字段名称。

160.2.5 支持的类型

Spring Data Cloud Datastore 支持常规字段和集合元素的以下类型:

TypeStored as
com.google.cloud.Timestampcom.google.cloud.datastore.TimestampValue
com.google.cloud.datastore.Blobcom.google.cloud.datastore.BlobValue
com.google.cloud.datastore.LatLngcom.google.cloud.datastore.LatLngValue
java.lang.Boolean , booleancom.google.cloud.datastore.BooleanValue
java.lang.Double , doublecom.google.cloud.datastore.DoubleValue
java.lang.Long , longcom.google.cloud.datastore.LongValue
java.lang.Integer , intcom.google.cloud.datastore.LongValue
java.lang.Stringcom.google.cloud.datastore.StringValue
com.google.cloud.datastore.Entitycom.google.cloud.datastore.EntityValue
com.google.cloud.datastore.Keycom.google.cloud.datastore.KeyValue
byte[]com.google.cloud.datastore.BlobValue
Java enumcom.google.cloud.datastore.StringValue

此外,支持所有可以通过org.springframework.core.convert.support.DefaultConversionService转换为表中列出的类型的类型。

160.2.6 自定义类型

可以使用自定义转换器来扩展对用户定义类型的类型支持。

  • 转换器需要双向实现org.springframework.core.convert.converter.Converter接口。

  • 用户定义的类型需要 Map 到 Cloud Datastore 支持的基本类型之一。

  • 这两个 Converter 的实例(读和写)都需要传递给DatastoreCustomConversions构造函数,然后必须将该_3 用作@Bean

For example:

我们希望在Singer POJO 上有一个类型为Album的字段,并希望将其存储为字符串属性:

@Entity
public class Singer {

	@Id
	String singerId;

	String name;

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

160.2.7 集合和数组

支持受支持类型的数组和集合(实现java.util.Collection的类型)。它们存储为com.google.cloud.datastore.ListValue。元素分别转换为 Cloud Datastore 支持的类型。 byte[]是一个 exception,它将转换为com.google.cloud.datastore.Blob

160.2.8 集合的自定义转换器

用户可以提供从List<?>到自定义集合类型的转换器。仅需要读取转换器,在写端使用 Collection API 将集合转换为内部列表类型。

集合转换器需要实现org.springframework.core.convert.converter.Converter接口。

Example:

让我们从前面的示例中改进 Singer 类。我们希望有一个ImmutableSet<Album>类型的字段,而不是Album类型的字段:

@Entity
public class Singer {

	@Id
	String singerId;

	String name;

	ImmutableSet<Album> albums;
}

我们只需要定义一个读转换器:

static final Converter<List<?>, ImmutableSet<?>> LIST_IMMUTABLE_SET_CONVERTER =
			new Converter<List<?>, ImmutableSet<?>>() {
				@Override
				public ImmutableSet<?> convert(List<?> source) {
					return ImmutableSet.copyOf(source);
				}
			};

并将其添加到自定义转换器列表中:

@Configuration
public class ConverterConfiguration {
	@Bean
	public DatastoreCustomConversions datastoreCustomConversions() {
		return new DatastoreCustomConversions(
				Arrays.asList(
						LIST_IMMUTABLE_SET_CONVERTER,

						ALBUM_STRING_CONVERTER,
						STRING_ALBUM_CONVERTER));
	}
}

160.3 Relationships

本节介绍了三种表示实体之间关系的方法:

  • 嵌入式实体直接存储在包含实体的字段中

  • @Descendant一对多关系的带 Comments 的属性

  • @Reference带 Comments 的常规关系属性,无层次结构

160.3.1 嵌入式实体

类型也用@EntityComments 的字段将转换为EntityValue并存储在父实体中。

以下是 Cloud Datastore 实体的示例,其中包含 JSON 中的嵌入式实体:

{
  "name" : "Alexander",
  "age" : 47,
  "child" : {"name" : "Philip"  }
}

这对应于一对简单的 Java 实体:

import org.springframework.cloud.gcp.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实体不是以其自己的类型存储的。它们全部存储在parents类型的child字段中。

支持多个级别的嵌入式实体。

Note

嵌入式实体不需要具有@Id字段,只有顶级实体才需要。

Example:

实体可以保存自己类型的嵌入式实体。我们可以使用此功能将树存储在 Cloud Datastore 中:

import org.springframework.cloud.gcp.data.datastore.core.mapping.Embedded;
import org.springframework.cloud.gcp.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;
  }
}

Maps

Map 将存储为嵌入式实体,其中键值成为嵌入式实体中的字段名称。这些 Map 中的值类型可以是任何常规支持的属性类型,并且将使用配置的转换器将键值转换为 String。

同样,可以嵌入实体的集合。写入时将转换为ListValue

Example:

代替上一个示例中的二叉树,我们想在 Cloud Datastore 中存储一棵普通树(每个节点可以有任意数量的子代)。为此,我们需要创建一个List<EmbeddableTreeNode>类型的字段:

import org.springframework.cloud.gcp.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;
  }
}

由于 Map 存储为实体,因此它们可以进一步保存嵌入式实体:

  • 值中的单个嵌入式对象可以存储在嵌入式 Map 的值中。

  • 值中嵌入对象的集合也可以存储为嵌入 Map 的值。

  • 值中的 Map 进一步存储为嵌入式实体,并对其值进行递归应用相同的规则。

160.3.2 祖辈关系

@DescendantsComments 支持父子关系。

与嵌入的子代不同,后代是驻留在自己种类中的完整实体。父实体没有额外的字段来保存后代实体。相反,该关系是在后代的键中捕获的,这些键引用了它们的父实体:

import org.springframework.cloud.gcp.data.datastore.core.mapping.Descendants;
import org.springframework.cloud.gcp.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;
}

例如,Item的 GQL 键 Literals 表示形式的实例还将包含父级ShoppingOrder ID 值:

Key(orders, '12345', purchased_item, 'eggs')

父级ShoppingOrder的 GQL 键 Literals 表示为:

Key(orders, '12345')

Cloud Datastore 实体以各自的种类单独存在。

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

使用 Datastore 的ancestor relationships将对象的父子关系结构存储在 Cloud Datastore 中。因为这些关系是由 Ancestor 机制定义的,所以父实体或子实体中都不需要额外的列来存储此关系。关系链接是后代实体键值的一部分。这些关系可能有很多层次。

拥有子实体的属性必须类似于集合,但它们可以是常规属性(如List,数组,Set等)支持的任何受支持的可相互转换的类似于集合的类型……子项的 ID 类型必须具有Key因为 Cloud Datastore 将祖先关系链接存储在子代键的内部。

读取或保存实体会自动导致分别读取或保存该实体下的所有子级。如果创建了一个新的子项并将其添加到带有 Comments@Descendants的属性中,并且 key 属性保留为空,则将为该子项分配新的密钥。检索到的子代的 Sequences 可能与保存的原始属性中的 Sequences 不同。

除非将子项的 key 属性设置为null或包含新父项作为祖先的值,否则子实体不能从一个父项的属性移到另一父项的属性。由于 Cloud Datastore 实体键可以有多个父实体,因此子实体可能出现在多个父实体的属性中。由于实体密钥在 Cloud Datastore 中是不可变的,因此要更改子项的密钥,您必须删除现有子项并用新密钥重新保存。

160.3.3 关键参考关系

常规关系可以使用@ReferenceComments 存储。

import org.springframework.cloud.gcp.data.datastore.core.mapping.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关系是指以各自种类存在的完整实体之间的关系。 ShoppingOrderItem实体之间的关系存储为ShoppingOrder内的 Key 字段,由 Spring Data Cloud Datastore 解析为基础 Java 实体类型:

{
  "id" : 12345,
  "specialSingleItem" : Key(item, "milk"),
  "items" : [ Key(item, "eggs"), Key(item, "sausage") ]
}

参考属性可以是单数或类似集合的。这些属性对应于实体和 Cloud Datastore Kind 中包含引用实体的键值的实际列。引用的实体是其他种类的成熟实体。

@Descendants关系类似,读取或写入实体将递归读取或写入所有级别的所有引用实体。如果引用的实体具有null ID 值,则它们将另存为新实体,并将具有 Cloud Datastore 分配的 ID 值。实体的密钥和实体作为引用持有的密钥之间没有关系的要求。从 Cloud Datastore 读回时,不会保留类似集合的参考属性的 Sequences。

160.4 数据存储区操作和模板

DatastoreOperations及其实现DatastoreTemplate提供了 Spring 开发人员熟悉的 Template 模式。

使用 Spring Boot Starter for Datastore 提供的自动配置,您的 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 提供了以下便捷方法:

  • 写操作(保存和删除)

  • Read-write transactions

160.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);

这些方法分别允许查询:使用所有相同的 Map 和转换功能由给定实体类 Map 的实体给定 Map 函数产生的任意类型*仅查询找到的实体的 Cloud Datastore 键

160.4.2 按 ID 查找

Datstore 读取一种类型的单个实体或多个实体。

您可以使用DatastoreTemplate执行读取,例如:

Trader trader = this.datastoreTemplate.findById("trader1", Trader.class);

List<Trader> traders = this.datastoreTemplate.findAllById(ImmutableList.of("trader1", "trader2"), Trader.class);

List<Trader> allTraders = this.datastoreTemplate.findAll(Trader.class);

Cloud Datastore 会执行具有高度一致性的基于键的读取,但最终会具有一致性的查询。在上面的示例中,前两次读取使用键,而第三次使用基于相应种类Trader的查询执行。

Indexes

默认情况下,所有字段都被索引。要禁用对特定字段的索引,可以使用@UnindexedComments。

Example:

import org.springframework.cloud.gcp.data.datastore.core.mapping.Unindexed;

public class ExampleItem {
	long indexedField;

	@Unindexed
	long unindexedField;
}

直接或通过查询方法使用查询时,如果 select 语句不是SELECT *WHERE子句中有多个过滤条件,则 Cloud Datastore 要求复合自定义索引

读取偏移量,限制和排序

DatastoreRepository和自定义实体存储库实现了 Spring Data PagingAndSortingRepository,该数据支持使用页码和页面大小的偏移量和限制。通过向findAll提供DatastoreQueryOptionsDatastoreTemplate也支持分页和排序选项。

Partial read

目前尚不支持此功能。

160.4.3 写入/更新

DatastoreOperations的 write 方法接受 POJO 并将其所有属性写入 Datastore。所需的数据存储类型和实体元数据是从给定对象的实际类型中获得的。

如果从数据存储区检索了 POJO,并且更改了其 ID 值,然后写入或更新了 POJO,则该操作就像针对具有新 ID 值的行一样进行。具有原始 ID 值的实体将不受影响。

Trader t = new Trader();
this.datastoreTemplate.save(t);

save方法的行为与更新或插入相同。

Partial Update

目前尚不支持此功能。

160.4.4 Transactions

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

performTransaction方法接受提供DatastoreOperations对象实例的Function。函数的最终返回值和类型由用户确定。您可以像常规DatastoreOperations一样使用此对象,但有一个 exception:

  • 它无法执行子 Transaction。

由于 Cloud Datastore 的一致性保证,因此事务内部使用的实体之间的操作和关系有limitations

使用@Transactional 注解的声明式事务

此功能需要使用spring-cloud-gcp-starter-data-datastore时提供的DatastoreTransactionManager bean。

DatastoreTemplateDatastoreRepository支持以@Transactional annotation作为事务的运行方法。如果用@TransactionalComments 的方法调用了也有 Comments 的另一个方法,则这两个方法将在同一事务中工作。 performTransaction不能在@Transactional带 Comments 的方法中使用,因为 Cloud Datastore 不支持事务内的事务。

160.4.5 对 Map 的读写支持

您可以直接在 Cloud Datastore 中读写数据,而可以使用Map<String, ?>类型的 Maps 代替实体对象。

Note

这与使用包含 Map 属性的实体对象不同。

Map 键用作数据存储区实体的字段名称,并且 Map 值转换为数据存储区支持的类型。仅支持简单类型(即不支持集合)。可以添加自定义值类型的转换器(请参见第 159.2.10 节,“自定义类型”部分)。

Example:

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

160.5 Repositories

Spring 数据仓库是可以减少样板代码的抽象。

For example:

public interface TraderRepository extends DatastoreRepository<Trader, String> {
}

Spring Data 生成指定接口的有效实现,可以将其自动连接到应用程序中。

DatastoreRepositoryTrader类型参数是指基础域类型。第二种类型参数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();
	}
}

存储库使您可以定义自定义查询方法(在以下各节中进行详细介绍),以基于过滤和分页参数来检索,计数和删除。过滤参数可以是您配置的自定义转换器支持的类型。

160.5.1 按约定查询方法

public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
  List<Trader> findByAction(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);
}

在上面的示例中,使用 https://docs.spring.io/spring-data/data-commons/docs/current/reference/html#repositories.query-methods 根据方法名称生成query methods in TradeRepository。 query-creation [Spring 数据查询创建命名约定]。

Cloud Datastore 仅支持通过 AND 连接的filter组件以及以下操作:

  • equals

  • greater than or equals

  • greater than

  • less than or equals

  • less than

  • is null

在编写仅指定这些方法签名的自定义存储库接口后,将为您生成实现,并且可以将其与存储库的自动关联实例一起使用。由于 Cloud Datastore 要求明确选择的字段必须全部一起出现在组合索引中,因此基于find的基于名称的查询方法以SELECT *的形式运行。

还支持删除查询。例如,诸如deleteByActionremoveByAction之类的查询方法会删除findByAction找到的实体。删除查询是作为单独的读取和删除操作执行的,而不是作为单个事务执行的,因为除非指定了查询的祖先,否则 Cloud Datastore 无法在事务中查询。因此,不能通过performInTransaction@TransactionalComments 在事务内部使用removeBydeleteBy名称约定查询方法。

删除查询可以具有以下返回类型:

  • 整数类型,表示已删除的实体数

  • 被删除的实体的集合

  • 'void'

方法可以具有org.springframework.data.domain.Pageable参数来控制分页和排序,或具有org.springframework.data.domain.Sort参数来仅控制排序。有关详情,请参见Spring Data 文档

为了在存储库方法中返回多个项目,我们支持 Java 集合以及org.springframework.data.domain.Pageorg.springframework.data.domain.Slice。如果方法的返回类型为org.springframework.data.domain.Page,则返回的对象将包括当前页面,结果总数和页面总数。

Note

返回Page的方法将执行附加查询以计算总页数。另一方面,返回Slice的方法不会执行任何其他查询,因此效率更高。

160.5.2 自定义 GQL 查询方法

可以通过以下两种方式之一将自定义 GQL 查询 Map 到存储库方法:

  • namedQueries属性文件

  • 使用@Query注解

带 Comments 的查询方法

使用@Query注解:

GQL 的标签名称与方法参数的@ParamComments 名称相对应。

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  test_entities_ci WHERE id = @id_val")
  TestEntity getOneTestEntity(@Param("id_val") long id);
}

支持以下参数类型:

  • 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以外,还支持每种类型的数组形式。

如果要获取查询的项数或查询返回的项,请分别设置@Query注解的count = trueexists = true属性。在这些情况下,查询方法的返回类型应为整数类型或布尔类型。

Cloud Datastore 提供的SELECT key FROM …特殊列适用于所有类型,可检索Keys of each row. Selecting this special key列对于countexists查询特别有用且高效。

您还可以查询非实体类型:

@Query(value = "SELECT __key__ from test_entities_ci")
	List<Key> getKeys();

	@Query(value = "SELECT __key__ from test_entities_ci limit 1")
	Key getKey();

	@Query("SELECT id FROM test_entities_ci WHERE id <= @id_val")
	List<String> getIds(@Param("id_val") long id);

	@Query("SELECT id FROM test_entities_ci WHERE id <= @id_val limit 1")
	String getOneId(@Param("id_val") long id);

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 注解中。也可以从域类的@EntityComments 中解析种类名称。

在这种情况下,查询应引用表名,该表名带有完全限定的类名,并用|个字符|fully.qualified.ClassName|包围。当 SpEL 表达式以提供给@EntityComments 的种类名称出现时,此功能很有用。例如:

@Query("SELECT * FROM |com.example.Trade| WHERE trades.action = @act")
List<Trade> fetchByActionNamedQuery(@Param("act") String action);

具有命名查询属性的查询方法

您还可以在属性文件中使用 Cloud Datastore 参数标签和 SpEL 表达式指定查询。

默认情况下,@EnableDatastoreRepositories上的namedQueriesLocation属性指向META-INF/datastore-named-queries.properties文件。您可以通过提供 GQL 作为“ interface.method”属性的值来在属性文件中指定对方法的查询:

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

}

160.5.3 Transactions

这些事务与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";
    }
  );
}

160.5.4 Projections

Spring Data Cloud Datastore 支持projections。您可以根据域类型定义投影接口,并添加在存储库中返回它们的查询方法:

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 查询,则可以进一步将从 Cloud Datastore 检索到的字段限制为仅投影所需的字段。但是,自定义的 select 语句(不使用SELECT *的语句)需要包含所选字段的复合索引。

使用 SpEL 定义的投影类型的属性为基础域对象使用固定名称target。结果,访问基础属性的格式为target.<property-name>

160.5.5 REST 存储库

通过 Spring Boot 运行时,只需将以下依赖项添加到 pom 文件中,即可将存储库作为 REST 服务公开:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

如果您希望配置参数(例如路径),则可以使用@RepositoryRestResourceComments:

@RepositoryRestResource(collectionResourceRel = "trades", path = "trades")
public interface TradeRepository extends DatastoreRepository<Trade, String[]> {
}

例如,您可以使用curl http://<server>:<port>/trades检索存储库中的所有Trade对象,或者通过curl http://<server>:<port>/trades/<trader_id>进行任何特定 Transaction。

您也可以使用curl -XPOST -H"Content-Type: application/json" -[email protected] http://<server>:<port>/trades/进行 Transaction,其中文件test.json包含Trade对象的 JSON 表示形式。

要删除 Transaction,您可以使用curl -XDELETE http://<server>:<port>/trades/<trader_id>

160.6 Sample

提供了简单的 Spring Boot 应用程序和更高级的Spring Boot 应用程序 samples,以显示如何使用 Spring Data Cloud Datastore Starter和模板。