Os objetos de dados no Datastore são conhecidos como entidades. Uma entidade tem uma ou mais propriedades com nome, cada uma das quais pode ter um ou mais valores. As entidades do mesmo tipo não têm de ter as mesmas propriedades e os valores de uma entidade para uma determinada propriedade não têm de ser todos do mesmo tipo de dados. (Se necessário, uma aplicação pode estabelecer e aplicar essas restrições no seu próprio modelo de dados.)
O Datastore suporta uma variedade de tipos de dados para valores de propriedades. Estas incluem, entre outras:
- Números inteiros
- Números de vírgula flutuante
- Strings
- Datas
- Dados binários
Para ver uma lista completa de tipos, consulte o artigo Propriedades e tipos de valores.
Cada entidade no Datastore tem uma chave que a identifica de forma exclusiva. A chave é composta pelos seguintes componentes:
- O espaço de nomes da entidade, que permite a multilocação
- O tipo da entidade, que a categoriza para efeitos de consultas do Datastore
- Um identificador para a entidade individual, que pode ser
- Uma string de nome da chave
- um ID numérico inteiro
- Um caminho do antepassado opcional que localiza a entidade na hierarquia do Datastore
Uma aplicação pode obter uma entidade individual do Datastore através da chave da entidade ou pode obter uma ou mais entidades emitindo uma consulta com base nas chaves ou nos valores das propriedades das entidades.
O SDK do App Engine Java inclui uma API simples, fornecida no pacote com.google.appengine.api.datastore
, que suporta diretamente as funcionalidades do Datastore. Todos os exemplos neste documento baseiam-se nesta API de baixo nível. Pode optar por usá-la diretamente na sua aplicação ou como base para criar a sua própria camada de gestão de dados.
O Datastore em si não aplica restrições à estrutura das entidades, como se uma determinada propriedade tem um valor de um tipo específico. Esta tarefa é deixada à aplicação.
Tipos e identificadores
Cada entidade do Datastore é de um tipo específico,que categoriza a entidade para fins de consultas: por exemplo, uma aplicação de recursos humanos pode representar cada funcionário de uma empresa com uma entidade do tipo Employee
. Na API Java Datastore, especifica o tipo de uma entidade quando a cria, como um argumento para o construtor Entity()
. Todos os nomes de tipos que começam com dois sublinhados (__
) estão reservados e não podem ser usados.
O exemplo seguinte cria uma entidade do tipo Employee
, preenche os respetivos valores de propriedades e guarda-a no Datastore:
Além de um tipo, cada entidade tem um identificador, atribuído quando a entidade é criada. Uma vez que faz parte da chave da entidade, o identificador está associado permanentemente à entidade e não pode ser alterado. Pode ser atribuído de duas formas:
- A sua aplicação pode especificar a sua própria string de nome da chave para a entidade.
- Pode fazer com que o Datastore atribua automaticamente à entidade um ID numérico inteiro.
Para atribuir um nome de chave a uma entidade, indique o nome como o segundo argumento ao construtor quando criar a entidade:
Para que o Datastore atribua automaticamente um ID numérico, omita este argumento:
Atribuir identificadores
O Datastore pode ser configurado para gerar IDs automáticos através de duas políticas de IDs automáticos diferentes:
- A política
default
gera uma sequência aleatória de IDs não usados que são distribuídos de forma aproximadamente uniforme. Cada ID pode ter até 16 dígitos decimais. - A política
legacy
cria uma sequência de IDs de números inteiros mais pequenos não consecutivos.
Se quiser apresentar os IDs das entidades ao utilizador e/ou depender da respetiva ordem, a melhor opção é usar a atribuição manual.
O Datastore gera uma sequência aleatória de IDs não usados que são distribuídos de forma aproximadamente uniforme. Cada ID pode ter até 16 dígitos decimais.
Caminhos de antecessores
As entidades no Cloud Datastore formam um espaço estruturado hierarquicamente semelhante à estrutura de diretórios de um sistema de ficheiros. Quando cria uma entidade, pode, opcionalmente, designar outra entidade como respetiva principal. A nova entidade é uma secundária da entidade principal (tenha em atenção que, ao contrário de um sistema de ficheiros, a entidade principal não tem de existir efetivamente). Uma entidade sem um elemento principal é uma entidade raiz. A associação entre uma entidade e a respetiva entidade principal é permanente e não pode ser alterada depois de a entidade ser criada. O Cloud Datastore nunca atribui o mesmo ID numérico a duas entidades com o mesmo elemento principal ou a duas entidades raiz (as que não têm um elemento principal).
O principal de uma entidade, o principal do principal e assim sucessivamente, são os seus ancestrais; os seus secundários, os secundários dos secundários e assim sucessivamente, são os seus descendentes. Uma entidade raiz e todos os respetivos descendentes pertencem ao mesmo grupo de entidades. A sequência de entidades que começa com uma entidade de raiz e prossegue do elemento principal para o elemento secundário, conduzindo a uma determinada entidade, constitui o caminho do antepassado dessa entidade. A chave completa que identifica a entidade consiste numa sequência de pares de tipo-identificador que especificam o respetivo caminho de antepassados e terminam com os da própria entidade:
[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]
Para uma entidade raiz, o caminho de antepassados está vazio e a chave consiste apenas no tipo e no identificador da própria entidade:
[Person:GreatGrandpa]
Este conceito é ilustrado pelo diagrama seguinte:
Para designar o elemento principal de uma entidade, forneça a chave do elemento principal como um argumento
para o construtor
Entity()
ao criar a entidade secundária. Pode obter a chave chamando o método getKey()
da entidade principal:
Se a nova entidade também tiver um nome de chave, indique o nome de chave como o segundo argumento para o construtor Entity()
e a chave da entidade principal como o terceiro argumento:
Transações e grupos de entidades
Todas as tentativas de criar, atualizar ou eliminar uma entidade ocorrem no contexto de uma transação. Uma única transação pode incluir qualquer número dessas operações. Para manter a consistência dos dados, a transação garante que todas as operações que contém são aplicadas ao Datastore como uma unidade ou, se alguma das operações falhar, que nenhuma delas é aplicada. Além disso, todas as leituras fortemente consistentes (consultas de antecessores ou obtenções) realizadas na mesma transação observam um instantâneo consistente dos dados.
Conforme mencionado acima, um grupo de entidades é um conjunto de entidades ligadas através da ascendência a um elemento raiz comum. A organização dos dados em grupos de entidades pode limitar as transações que podem ser realizadas:
- Todos os dados acedidos por uma transação têm de estar contidos num máximo de 25 grupos de entidades.
- Se quiser usar consultas numa transação, os seus dados têm de estar organizados em grupos de entidades de forma que possa especificar filtros de antecessores que correspondam aos dados certos.
- Existe um limite de débito de gravação de cerca de uma transação por segundo num único grupo de entidades. Esta limitação existe porque o Datastore executa a replicação síncrona sem mestre de cada grupo de entidades numa vasta área geográfica para oferecer elevada fiabilidade e tolerância a falhas.
Em muitas aplicações, é aceitável usar a consistência eventual (ou seja, uma consulta não ancestral que abranja vários grupos de entidades, que pode, por vezes, devolver dados ligeiramente desatualizados) quando obtém uma vista geral de dados não relacionados e, em seguida, usar a consistência forte (uma consulta ancestral ou um get
de uma única entidade) quando visualiza ou edita um único conjunto de dados altamente relacionados. Em tais aplicações, é normalmente uma boa abordagem usar um grupo de entidades separado para cada conjunto de dados altamente relacionados.
Para mais informações, consulte o artigo Estruturar para uma forte consistência.
Propriedades e tipos de valores
Os valores de dados associados a uma entidade consistem numa ou mais propriedades. Cada propriedade tem um nome e um ou mais valores. Uma propriedade pode ter valores de mais do que um tipo, e duas entidades podem ter valores de tipos diferentes para a mesma propriedade. As propriedades podem ser indexadas ou não indexadas (as consultas que ordenam ou filtram uma propriedade P ignoram as entidades em que P não está indexada). Uma entidade pode ter, no máximo, 20 000 propriedades indexadas.
São suportados os seguintes tipos de valores:
Tipo de valor | Tipos de Java | Ordenação | Notas |
---|---|---|---|
Número inteiro | short int long java.lang.Short java.lang.Integer java.lang.Long |
Numérico | Armazenado como número inteiro longo e, em seguida, convertido no tipo de campo Os valores fora do intervalo transbordam |
Número de vírgula flutuante | float double java.lang.Float java.lang.Double |
Numérico | Precisão dupla de 64 bits, IEEE 754 |
Booleano | boolean java.lang.Boolean |
false <true |
|
String de texto (curta) | java.lang.String |
Unicode | Até 1500 bytes Os valores superiores a 1500 bytes geram IllegalArgumentException |
String de texto (longa) | com.google.appengine.api.datastore.Text |
Nenhum | Até 1 megabyte Não indexado |
String de bytes (curta) | com.google.appengine.api.datastore.ShortBlob |
Ordem dos bytes | Até 1500 bytes Os valores superiores a 1500 bytes geram um erro IllegalArgumentException |
String de bytes (longa) | com.google.appengine.api.datastore.Blob |
Nenhum | Até 1 megabyte Não indexado |
Data e hora | java.util.Date |
Cronológico | |
Ponto geográfico | com.google.appengine.api.datastore.GeoPt |
Por latitude, depois longitude |
|
Morada | com.google.appengine.api.datastore.PostalAddress |
Unicode | |
Número de telefone | com.google.appengine.api.datastore.PhoneNumber |
Unicode | |
Endereço de email | com.google.appengine.api.datastore.Email |
Unicode | |
Utilizador da Conta Google | com.google.appengine.api.users.User |
Endereço de email por ordem Unicode |
|
Identificador de mensagens instantâneas | com.google.appengine.api.datastore.IMHandle |
Unicode | |
Link | com.google.appengine.api.datastore.Link |
Unicode | |
Categoria | com.google.appengine.api.datastore.Category |
Unicode | |
Classificação | com.google.appengine.api.datastore.Rating |
Numérico | |
Chave do armazenamento de dados | com.google.appengine.api.datastore.Key ou o objeto referenciado (como secundário) |
Por elementos de caminho (tipo, identificador, tipo, identificador...) |
Até 1500 bytes Os valores superiores a 1500 bytes geram um erro IllegalArgumentException |
Chave do Blobstore | com.google.appengine.api.blobstore.BlobKey |
Ordem dos bytes | |
Entidade incorporada | com.google.appengine.api.datastore.EmbeddedEntity |
Nenhum | Não indexada |
Nulo | null |
Nenhum |
Importante: recomendamos vivamente que evite armazenar um users.User
como valor de propriedade, uma vez que inclui o endereço de email juntamente com o ID exclusivo. Se um utilizador alterar o respetivo endereço de email e comparar o valor user.User
antigo armazenado com o novo valor user.User
, não vai haver correspondência. Em alternativa, use o User
valor do ID do utilizador como identificador exclusivo estável do utilizador.
Para strings de texto e dados binários não codificados (strings de bytes), o Datastore suporta dois tipos de valores:
- As strings curtas (até 1500 bytes) são indexadas e podem ser usadas em condições de filtro de consultas e ordens de ordenação.
- As strings longas (até 1 megabyte) não são indexadas e não podem ser usadas em filtros de consultas nem ordens de ordenação.
Blob
na API Datastore. Este tipo não está relacionado com blobs usados na API Blobstore.
Quando uma consulta envolve uma propriedade com valores de tipos mistos, o Datastore usa uma ordenação determinística com base nas representações internas:
- Valores nulos
- Números de ponto fixo
- Números inteiros
- Datas e horas
- Classificações
- Valores booleanos
- Sequências de bytes
- String de bytes
- String Unicode
- Chaves do Blobstore
- Números de vírgula flutuante
- Pontos geográficos
- Utilizadores de Contas Google
- Chaves do Datastore
Uma vez que as strings de texto longas, as strings de bytes longas e as entidades incorporadas não são indexadas, não têm uma ordenação definida.
Trabalhar com entidades
As aplicações podem usar a API Datastore para criar, obter, atualizar e eliminar entidades. Se a aplicação souber a chave completa de uma entidade (ou puder derivá-la da respetiva chave principal, tipo e identificador), pode usar a chave para operar diretamente na entidade. Uma aplicação também pode obter a chave de uma entidade como resultado de uma consulta do Datastore. Consulte a página Consultas do Datastore para mais informações.
A API Java Datastore usa métodos da interface DatastoreService
para operar em entidades. Obtém um objeto DatastoreService
chamando o método estático DatastoreServiceFactory.getDatastoreService()
:
Criar uma entidade
Pode criar uma nova entidade construindo uma instância da classe Entity
, fornecendo o tipo da entidade como um argumento ao construtor Entity()
.
Depois de preencher as propriedades da entidade, se necessário, guarde-a
no arquivo de dados transmitindo-a como um argumento ao método
DatastoreService.put()
. Pode especificar o nome da chave da entidade transmitindo-o como o segundo argumento ao construtor:
Se não fornecer um nome de chave, o Datastore gera automaticamente um ID numérico para a chave da entidade:
Obter uma entidade
Para obter uma entidade identificada por uma determinada chave, transmita o objeto Key
ao método DatastoreService.get()
:
Atualizar uma entidade
Para atualizar uma entidade existente, modifique os atributos do objeto Entity e, em seguida, transmita-o para o método DatastoreService.put()
. Os dados do objeto substituem a entidade existente. O objeto completo é enviado para o Datastore com cada chamada para put()
.
Eliminar uma entidade
Dada a chave de uma entidade, pode eliminar a entidade com o método DatastoreService.delete()
:
Propriedades repetidas
Pode armazenar vários valores numa única propriedade.
Entidades incorporadas
Por vezes, pode ser conveniente incorporar uma entidade como propriedade de outra entidade. Isto pode ser útil, por exemplo, para criar uma estrutura hierárquica de valores de propriedades numa entidade. A classe Java EmbeddedEntity
permite-lhe fazer o seguinte:
Quando uma entidade incorporada é incluída em índices, pode consultar subpropriedades. Se excluir uma entidade incorporada da indexação, todas as subpropriedades também são excluídas da indexação. Opcionalmente, pode associar uma chave a uma entidade incorporada, mas (ao contrário de uma entidade completa) a chave não é obrigatória e, mesmo que esteja presente, não pode ser usada para obter a entidade.
Em vez de preencher manualmente as propriedades da entidade incorporada, pode usar o método setPropertiesFrom()
para as copiar de uma entidade existente:
Posteriormente, pode usar o mesmo método para recuperar a entidade original da entidade incorporada:
Operações em lote
Os DatastoreService
métodos put()
, get()
,
e delete()
(e as respetivas contrapartes AsyncDatastoreService) têm versões em lote que aceitam um objeto iterável (da classe Entity
para put()
, Key
para get()
e delete()
) e usam-no para operar em várias entidades numa única chamada do Datastore:
Estas operações em lote agrupam todas as entidades ou chaves por grupo de entidades e, em seguida, executam a operação pedida em cada grupo de entidades em paralelo. Estas chamadas em lote são mais rápidas do que fazer chamadas separadas para cada entidade individual, porque incorrem na sobrecarga de apenas uma chamada de serviço. Se estiverem envolvidos vários grupos de entidades, o trabalho de todos os grupos é realizado em paralelo no lado do servidor.
Gerar chaves
As aplicações podem usar a classe KeyFactory
para criar um objeto Key
para uma entidade a partir de componentes conhecidos, como o tipo e o identificador da entidade. Para uma entidade sem principal, transmita o tipo e o identificador (uma string de nome de chave ou um ID numérico) para o método estático KeyFactory.createKey()
para criar a chave. Os exemplos seguintes criam uma chave para uma entidade do tipo Person
com o nome da chave "GreatGrandpa"
ou o ID numérico 74219
:
Se a chave incluir um componente de caminho, pode usar a classe auxiliar KeyFactory.Builder
para criar o caminho. O método addChild
desta classe adiciona uma única entidade ao caminho e devolve o próprio criador, para que possa encadear uma série de chamadas, começando pela entidade raiz, para criar o caminho uma entidade de cada vez. Depois de criar o caminho completo, chame getKey
para obter a chave resultante:
A classe KeyFactory
também inclui os métodos estáticos keyToString
e stringToKey
para a conversão entre chaves e as respetivas representações de strings:
A representação de string de uma chave é "segura para a Web": não contém carateres considerados especiais em HTML ou em URLs.
Usar uma lista vazia
Historicamente, o Datastore não tinha uma representação para uma propriedade que representasse uma lista vazia. O SDK Java contornou este problema armazenando coleções vazias como valores nulos, pelo que não existe forma de distinguir entre valores nulos e listas vazias. Para manter a retrocompatibilidade, este continua a ser o comportamento predefinido, resumido da seguinte forma:- As propriedades nulas são escritas como nulas no Datastore
- As coleções vazias são escritas como nulas no Datastore
- Um valor nulo é lido como nulo do Armazenamento de dados
- Uma coleção vazia é lida como nula.
No entanto, se alterar o comportamento predefinido, o SDK para Java suporta o armazenamento de listas vazias. Recomendamos que considere as implicações de alterar o comportamento predefinido da sua aplicação e, em seguida, ative o suporte para listas vazias.
Para alterar o comportamento predefinido para que possa usar listas vazias, defina a propriedade DATASTORE_EMPTY_LIST_SUPPORT durante a inicialização da app da seguinte forma:
System.setProperty(DatastoreServiceConfig.DATASTORE_EMPTY_LIST_SUPPORT, Boolean.TRUE.toString());
Com esta propriedade definida como true
, conforme mostrado acima:
- As propriedades nulas são escritas como nulas no Datastore
- As coleções vazias são escritas como listas vazias no Datastore
- Um valor nulo é lido como nulo do Armazenamento de dados
- Quando lê a partir do Datastore, é devolvida uma lista vazia como uma coleção vazia.