Entidades, propriedades e chaves

Os objetos de dados no Cloud Datastore são conhecidos como entidades. Uma entidade tem uma ou mais propriedades nomeadas, que podem ter um ou mais valores. Entidades do mesmo tipo não precisam ter as mesmas propriedades. Além disso, os valores de uma entidade para uma determinada propriedade não precisam ser todos do mesmo tipo de dados. Se necessário, um aplicativo pode estabelecer e aplicar essas restrições no próprio modelo de dados.

O Cloud Datastore é compatível com uma grande variedade de tipos de dados para valores de propriedade. Eles incluem, entre outros:

  • Números inteiros
  • Números de ponto flutuante
  • Strings
  • Datas
  • Dados binários

Para uma lista completa de tipos, consulte Propriedades e tipos de valor.

Cada entidade no Cloud Datastore tem uma chave que a identifica de maneira exclusiva. A chave consiste nos seguintes componentes:

  • O namespace da entidade, que permite a multilocação
  • O tipo da entidade, que a categoriza para as consultas do Cloud Datastore
  • Um identificador da entidade individual, que pode ser:
    • uma string de nome da chave;
    • um código numérico inteiro.
  • Um caminho ancestral opcional que localiza a entidade na hierarquia do Cloud Datastore

Um aplicativo pode buscar uma entidade individual do Cloud Datastore usando a chave dela ou recuperar uma ou mais entidades emitindo uma consulta com base nas chaves ou nos valores de propriedade delas.

O SDK do App Engine para Java inclui uma API simples, fornecida no pacote com.google.appengine.api.datastore, que é diretamente compatível com os recursos do Cloud Datastore. Todos os exemplos neste documento são baseados nessa API de baixo nível. Você pode optar por usá-la diretamente no seu aplicativo ou como base para construir a própria camada de gerenciamento de dados.

O Cloud Datastore em si não impõe nenhuma restrição sobre a estrutura das entidades, como, por exemplo, se determinada propriedade tem um valor de um tipo específico. Essa tarefa é do aplicativo.

Tipos e identificadores

Cada entidade do Cloud Datastore tem um tipo determinado que a categoriza para fins de consultas. Por exemplo, um aplicativo de recursos humanos pode representar cada funcionário de uma empresa com uma entidade do tipo Employee. Na API Java Datastore, você especifica o tipo da entidade quando a cria, como um argumento para o construtor Entity(). Todos os nomes de tipo que começam com dois sublinhados (__) são reservados e não podem ser usados.

Veja no exemplo a seguir como criar uma entidade do tipo Employee, preencher os valores de propriedade e salvá-la no Datastore:

Entity employee = new Entity("Employee", "asalieri");
employee.setProperty("firstName", "Antonio");
employee.setProperty("lastName", "Salieri");
employee.setProperty("hireDate", new Date());
employee.setProperty("attendedHrTraining", true);

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
datastore.put(employee);

Além de um tipo, cada entidade tem um identificador, que é atribuído quando ela é criada. Como ele é parte da chave da entidade, o identificador é associado permanentemente à entidade e não pode ser alterado. Ele pode ser atribuído de duas formas:

  • Seu aplicativo pode especificar sua própria string de nome de chave para a entidade.
  • O Cloud Datastore pode atribuir automaticamente um código numérico inteiro à entidade.

Para atribuir um nome de chave a uma entidade, forneça o nome como o segundo argumento ao construtor quando criar a entidade:

Entity employee = new Entity("Employee", "asalieri");

Para que Cloud Datastore atribua um código numérico automaticamente, omita esse argumento:

Entity employee = new Entity("Employee");

Como atribuir identificadores

O Cloud Datastore pode ser configurado para gerar códigos automaticamente usando duas políticas de identificação automática diferentes:

  • A política default gera uma sequência aleatória de códigos não utilizados que são distribuídos uniformemente de forma aproximada. Cada código pode ter até 16 dígitos decimais.
  • A política legacy cria uma sequência de códigos inteiros menores não consecutivos.

Se você quiser exibir os códigos de entidade para o usuário e/ou depender da ordem deles, a melhor coisa a fazer é usar a alocação manual.

O Cloud Datastore gera uma sequência aleatória de códigos não utilizados que são distribuídos de maneira quase uniforme. Cada código pode ter até 16 dígitos decimais.

Os valores de código atribuídos pelo sistema são garantidamente exclusivos para o grupo de entidades. Se você copiar uma entidade de um grupo de entidades ou namespace para outro e quiser preservar a parte do código da chave, primeiro atribua o código para impedir que o Cloud Datastore o selecione em uma atribuição futura.

Caminhos ancestrais

As entidades do Cloud Datastore formam um espaço hierarquicamente estruturado, semelhante à estrutura de diretórios de um sistema de arquivos. Ao criar uma entidade, é possível designar outra entidade como pai e a nova é a entidade filho (observe que, diferentemente do que ocorre em um sistema de arquivos, a entidade pai não precisa existir de verdade). Uma entidade sem pai é uma entidade raiz. A associação entre uma entidade e a entidade pai é permanente e não pode ser alterada depois que a entidade é criada. O Cloud Datastore nunca atribuirá o mesmo código numérico a duas entidades com o mesmo pai ou a duas entidades raiz (sem pai).

O pai de uma entidade, o pai do pai, e assim por diante são ancestrais dela. O filho, o filho do filho, e assim por diante são descendentes dela. Uma entidade raiz e todos os descendentes pertencem ao mesmo grupo de entidades. A sequência de entidades começando com uma entidade raiz e procedendo de pai para filho, levando a uma determinada entidade, constitui o caminho ancestral dessa entidade. A chave completa que identifica a entidade consiste em uma sequência de pares de identificadores de tipo que especifica seu caminho ancestral e termina com os da própria entidade:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

Para uma entidade raiz, o caminho ancestral está vazio, e a chave consiste unicamente no próprio tipo e identificador da entidade:

[Person:GreatGrandpa]

Este conceito é ilustrado pelo seguinte diagrama:

Mostra o relacionamento da entidade raiz com entidades filho no grupo de entidades

Para designar o pai de uma entidade, informe a chave da entidade pai como argumento do construtor Entity() ao criar a entidade filho. Para receber a chave, chame o método getKey() da entidade pai:

Entity employee = new Entity("Employee");
datastore.put(employee);

Entity address = new Entity("Address", employee.getKey());
datastore.put(address);

Se a nova entidade também tiver um nome de chave, forneça esse nome como o segundo argumento para o construtor Entity() e a chave da entidade mãe como o terceiro argumento:

Entity address = new Entity("Address", "addr1", employee.getKey());

Transações e grupos de entidades

Toda tentativa de criar, atualizar ou excluir uma entidade ocorre no contexto de uma transação. Uma única transação inclui qualquer número dessas operações. Para manter a consistência dos dados, a transação garante a aplicação de todas as suas operações no Cloud Datastore como uma unidade ou, em caso de falha de alguma operação, que nenhuma delas seja aplicada. Além disso, todas as leituras com consistência forte (consultas de ancestral ou operações de get), executadas na mesma transação, seguem um instantâneo consistente dos dados.

Como mencionado acima, um grupo de entidades é um conjunto de entidades conectadas por meio de um ancestral a um elemento raiz comum. A organização dos dados em grupos de entidades pode limitar quais transações podem ser realizadas:

  • Todos os dados acessados por uma transação devem estar contidos em, no máximo, 25 grupos de entidades.
  • Se você quiser usar consultas em uma transação, os dados precisam estar organizados em grupos de entidades para que você possa especificar filtros de ancestral que corresponderão aos dados corretos.
  • Há um limite de capacidade de gravação de aproximadamente uma transação por segundo em um único grupo de entidades. Essa limitação existe porque o Cloud Datastore executa a replicação síncrona sem mestre de cada grupo de entidades em uma ampla área geográfica para fornecer alta confiabilidade e tolerância a falhas.

Em muitos aplicativos, é aceitável usar a consistência eventual (ou seja, uma consulta que não seja de ancestral abrangendo vários grupos de entidades, que às vezes pode retornar dados um pouco desatualizados) ao ter uma visualização ampla dos dados não relacionados, e na sequência usar a consistência forte (uma consulta de ancestral ou uma operação get para uma única entidade) ao visualizar ou editar um único conjunto de dados altamente relacionados. Em tais aplicativos, geralmente é uma boa abordagem usar um grupo de entidades separado para cada conjunto de dados altamente relacionados. Para mais informações, consulte Como estruturar uma consistência forte.

Propriedades e tipos de valores

Os valores de dados associados a uma entidade consistem em uma ou mais propriedades. Cada propriedade tem um nome e um ou mais valores. Uma propriedade pode ter valores de mais de um tipo, e duas entidades podem ter valores de tipos diferentes para a mesma propriedade. As propriedades podem ser indexadas ou não (consultas que ordenam ou filtram com base em uma propriedade P ignoram entidades em que P não seja indexada). Uma entidade pode ter no máximo 20.000 propriedades indexadas.

Estes são os tipos de valor compatíveis:

Tipo de valor Tipo(s) de Java Ordem de classificação Observações
Número inteiro short
int
long
java.lang.Short
java.lang.Integer
java.lang.Long
Numérica Armazenado como número inteiro longo e convertido no tipo de campo

Excesso de valores fora do intervalo
Número de ponto flutuante float
double
java.lang.Float
java.lang.Double
Numérica 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

Valores superiores a 1500 bytes geram IllegalArgumentException
String de texto (longa) com.google.appengine.api.datastore.Text Nenhuma Até 1 megabyte

Não indexado
String de bytes (curta) com.google.appengine.api.datastore.ShortBlob Ordem de bytes Até 1500 bytes

Valores superiores a 1500 bytes geram IllegalArgumentException
String de bytes (longa) com.google.appengine.api.datastore.Blob Nenhuma Até 1 megabyte

Não indexado
Data e hora java.util.Date Cronológica
Ponto geográfico com.google.appengine.api.datastore.GeoPt Por latitude
e depois por longitude
Endereço postal com.google.appengine.api.datastore.PostalAddress Unicode
Número de telefone com.google.appengine.api.datastore.PhoneNumber Unicode
Endereço de e-mail com.google.appengine.api.datastore.Email Unicode
Usuário das Contas do Google com.google.appengine.api.users.User Endereço de e-mail
em 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érica
Chave do Cloud Datastore com.google.appengine.api.datastore.Key
ou o objeto referenciado (como um filho)
Por elementos de caminho
(tipo, identificador,
tipo, identificador...)
Até 1500 bytes

Valores superiores a 1500 bytes geram IllegalArgumentException
Chave Blobstore com.google.appengine.api.blobstore.BlobKey Ordem de bytes
Entidade incorporada com.google.appengine.api.datastore.EmbeddedEntity Nenhuma Não indexado
Nulo null Nenhuma

Importante: é altamente recomendável evitar o armazenamento de users.User como valor de propriedade porque isso agrega o endereço de e-mail ao código exclusivo. Se um usuário alterar o endereço de e-mail e você comparar o user.User antigo e armazenado com o novo valor user.User, eles não serão correspondentes. Em vez disso, use o User User como o identificador estável e exclusivo do usuário.

Para strings de texto e dados binários não codificados (strings de bytes), o Cloud Datastore aceita dois tipos de valor:

  • Strings curtas (até 1500 bytes) são indexadas e podem ser usadas em condições de filtro de consulta e ordens de classificação.
  • Strings longas (até 1 megabyte) não são indexadas e não podem ser usadas em filtros de consulta e ordens de classificação.
Observação: o tipo longo de string de bytes é denominado Blob na API do Cloud Datastore. Esse tipo não está relacionado aos blobs usados na API Blobstore.

Quando uma consulta inclui uma propriedade com valores de tipos mistos, o Cloud Datastore usa uma ordem determinística baseada nas representações internas:

  1. Valores nulos
  2. Números de ponto fixo
    • Números inteiros
    • Datas e horas
    • Classificações
  3. Valores booleanos
  4. Sequências de bytes
    • String de bytes
    • String Unicode
    • Chaves do Blobstore
  5. Números de ponto flutuante
  6. Pontos geográficos
  7. Usuários das Contas do Google
  8. Chaves do Cloud Datastore

Como strings de texto longas, strings de bytes longas e entidades incorporadas não são indexadas, elas não têm nenhuma ordem definida.

Como trabalhar com entidades

Os aplicativos podem usar a API Cloud Datastore para criar, recuperar, atualizar e excluir entidades. Se o aplicativo souber a chave completa de uma entidade (ou puder derivá-la da própria chave mãe, do tipo e do identificador), ele poderá usá-la para operar diretamente na entidade. Um aplicativo também pode conseguir a chave de uma entidade por meio de uma consulta do Cloud 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. Para conseguir um objeto DatastoreService, chame o método estático DatastoreServiceFactory.getDatastoreService():

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Como criar uma entidade

Você pode criar uma nova entidade construindo uma instância da classe Entity, fornecendo o tipo da entidade como um argumento para o construtor Entity().

Depois de preencher as propriedades da entidade, se necessário, salve-a no armazenamento de dados passando-a como um argumento para o método DatastoreService.put(). Você pode especificar o nome da chave da entidade passando-o como o segundo argumento para o construtor.

Entity employee = new Entity("Employee", "asalieri");
// Set the entity properties.
// ...
datastore.put(employee);

Se o nome de chave não for fornecido, o Cloud Datastore gerará automaticamente um código numérico para a chave da entidade:

Entity employee = new Entity("Employee");
// Set the entity properties.
// ...
datastore.put(employee);

Como recuperar uma entidade

Para recuperar uma entidade identificada por uma determinada chave, transmita o objeto Key ao método DatastoreService.get():

// Key employeeKey = ...;
Entity employee = datastore.get(employeeKey);

Como atualizar uma entidade

Para atualizar uma entidade atual, modifique os atributos do objeto Entity e, em seguida, passe-o ao método DatastoreService.put(). Os dados do objeto sobrescrevem a entidade atual. O objeto inteiro é enviado ao Cloud Datastore a cada chamada do método put().

Como excluir uma entidade

A partir de uma chave de entidade, é possível excluir a entidade com o método DatastoreService.delete():

// Key employeeKey = ...;
datastore.delete(employeeKey);

Propriedades repetidas

É possível armazenar diversos valores dentro de uma única propriedade.

Entity employee = new Entity("Employee");
ArrayList<String> favoriteFruit = new ArrayList<String>();
favoriteFruit.add("Pear");
favoriteFruit.add("Apple");
employee.setProperty("favoriteFruit", favoriteFruit);
datastore.put(employee);

// Sometime later
employee = datastore.get(employee.getKey());
@SuppressWarnings("unchecked") // Cast can't verify generic type.
    ArrayList<String> retrievedFruits = (ArrayList<String>) employee
    .getProperty("favoriteFruit");

Entidades incorporadas

Às vezes, pode ser conveniente incorporar uma entidade como propriedade de outra entidade. Isso pode ser útil, por exemplo, para criar uma estrutura hierárquica de valores de propriedade dentro de uma entidade. A classe Java EmbeddedEntity permite que você faça isso:

// Entity employee = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setProperty("homeAddress", "123 Fake St, Made, UP 45678");
embeddedContactInfo.setProperty("phoneNumber", "555-555-5555");
embeddedContactInfo.setProperty("emailAddress", "test@example.com");

employee.setProperty("contactInfo", embeddedContactInfo);

Quando uma entidade incorporada está incluída nos índices, é possível consultar subpropriedades. Se você excluir da indexação uma entidade incorporada, todas as subpropriedades também serão excluídas da indexação. Você também pode associar uma chave a uma entidade incorporada, mas, ao contrário de uma entidade completa, a chave não é necessária e, mesmo presente, não pode ser usada para recuperar a entidade.

Em vez de preencher manualmente as propriedades da entidade incorporada, use o método setPropertiesFrom() para copiá-las de uma entidade atual:

// Entity employee = ...;
// Entity contactInfo = ...;
EmbeddedEntity embeddedContactInfo = new EmbeddedEntity();

embeddedContactInfo.setKey(contactInfo.getKey()); // Optional, used so we can recover original.
embeddedContactInfo.setPropertiesFrom(contactInfo);

employee.setProperty("contactInfo", embeddedContactInfo);

Você pode usar o mesmo método depois para recuperar a entidade original da entidade incorporada:

Entity employee = datastore.get(employeeKey);
EmbeddedEntity embeddedContactInfo = (EmbeddedEntity) employee.getProperty("contactInfo");

Key infoKey = embeddedContactInfo.getKey();
Entity contactInfo = new Entity(infoKey);
contactInfo.setPropertiesFrom(embeddedContactInfo);

Operações em lote

Os métodos DatastoreService put(), get() e delete() (e suas correspondências AsyncDatastoreService) têm versões em lote que aceitam um objeto iterável (da classe Entity para put(), de Key para get() e delete()) e o utilizam para operar em várias entidades em uma única chamada do Cloud Datastore:

Entity employee1 = new Entity("Employee");
Entity employee2 = new Entity("Employee");
Entity employee3 = new Entity("Employee");
// ...

List<Entity> employees = Arrays.asList(employee1, employee2, employee3);
datastore.put(employees);

Essas operações em lote agrupam todas as entidades ou chaves por grupo de entidades e, em seguida, executam a operação solicitada em cada grupo de entidades em paralelo. Essas chamadas em lote são mais rápidas do que fazer chamadas separadas para cada entidade porque elas geram sobrecarga para apenas uma chamada de serviço. Se vários grupos de entidades estiverem envolvidos, o trabalho para todos os grupos é executado em paralelo no servidor.

Como gerar chaves

Os aplicativos 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. No caso de uma entidade sem pai, transmita o tipo e o identificador (que podem ser uma string de nome de chave ou um código numérico) ao método estático KeyFactory.createKey() para criar a chave. Os exemplos a seguir criam uma chave para uma entidade Person de tipo com o nome da chave "GreatGrandpa" ou código numérico 74219:

Key k1 = KeyFactory.createKey("Person", "GreatGrandpa");
Key k2 = KeyFactory.createKey("Person", 74219);

Se a chave incluir um componente de caminho, use a classe auxiliar KeyFactory.Builder para criar o caminho. Com o método addChild dessa classe, uma única entidade é adicionada ao caminho e o próprio criador é retornado. Assim, é possível encadear uma série de chamadas, começando com a entidade raiz, para criar o caminho com uma entidade por vez. Depois de criar o caminho completo, chame getKey para recuperar a chave resultante:

Key k =
    new KeyFactory.Builder("Person", "GreatGrandpa")
        .addChild("Person", "Grandpa")
        .addChild("Person", "Dad")
        .addChild("Person", "Me")
        .getKey();

A classe KeyFactory também inclui os métodos estáticos keyToString e stringToKey para a conversão entre chaves e as respectivas representações de string:

String personKeyStr = KeyFactory.keyToString(k);

// Some time later (for example, after using personKeyStr in a link).
Key personKey = KeyFactory.stringToKey(personKeyStr);
Entity person = datastore.get(personKey);

A representação de string de uma chave é "segura para Web": não contém caracteres considerados especiais em HTML nem em URLs.

Como usar uma lista vazia

Historicamente, o Cloud Datastore não tinha uma representação para uma propriedade que representasse uma lista vazia. O SDK do Java contornou isso armazenando coleções vazias como valores nulos. Portanto, não há como fazer a distinção entre valores nulos e listas vazias. Para manter a compatibilidade com versões anteriores, isso continua sendo o comportamento padrão, resumido da seguinte maneira:

  • As propriedades nulas são gravadas como nulas no Cloud Datastore.
  • As coleções vazias são gravadas como nulas no Cloud Datastore.
  • Um nulo é lido como nulo pelo Cloud Datastore.
  • Uma coleção vazia é lida como nula.

No entanto, se você alterar o comportamento padrão, o Java SDK aceitará o armazenamento de listas vazias. Recomendamos que considere as implicações de alterar o comportamento padrão do aplicativo e ativar o suporte para listas vazias.

Para alterar o comportamento padrão e poder usar listas vazias, defina a propriedade DATASTORE_EMPTY_LIST_SUPPORT durante a inicialização do aplicativo da seguinte maneira:

System.setProperty(DatastoreServiceConfig.DATASTORE_EMPTY_LIST_SUPPORT, Boolean.TRUE.toString());

Com essa propriedade definida como true conforme mostrado acima:

  • As propriedades nulas são gravadas como nulas no Cloud Datastore.
  • As coleções vazias são gravadas como uma lista vazia no Cloud Datastore.
  • Um nulo é lido como nulo pelo Cloud Datastore.
  • Durante a leitura pelo Cloud Datastore, uma lista vazia é retornada como uma coleção vazia.
Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Ambiente padrão do App Engine para Java