Consultas do Datastore na JDO

Este documento tem como foco o uso da biblioteca de persistência Java Data Objects (JDO) (em inglês) para consultas do App Engine Datastore. Para ver informações gerais sobre consultas, consulte a página principal Consultas do Datastore.

Uma consulta recupera entidades do Cloud Datastore que atendem a um conjunto específico de condições. A consulta é aplicada a entidades de um determinado tipo e especifica filtros nos valores de propriedades, chaves e ancestrais das entidades. Além disso, ela retorna como resultados zero ou mais entidades. Também especifica ordens de classificação para colocar os resultados em sequência pelos respectivos valores de propriedade. Os resultados incluem todas as entidades que tenham pelo menos um valor (que pode ser nulo) para cada propriedade nomeada nos filtros e ordens de classificação e que também tenham valores de propriedade que atendam a todos os critérios de filtro especificados. A consulta pode retornar entidades de projeção, inteiras ou apenas chaves de entidade.

Uma consulta típica inclui:

Quando executada, a consulta recupera todas as entidades do tipo determinado que satisfaçam a todos os filtros fornecidos, classificadas na ordem especificada. As consultas são executadas no modo de somente leitura.

Observação: para economizar memória e melhorar o desempenho, uma consulta precisa, sempre que possível, especificar um limite para o número de resultados retornados.

Observação: o mecanismo de consulta baseado em índice é compatível com uma grande variedade de consultas e adequado à maioria dos aplicativos. No entanto, ele não aceita alguns tipos de consulta comuns em outras tecnologias de banco de dados. Em especial, as consultas agregadas e conjuntas não são compatíveis com o mecanismo de consulta do Cloud Datastore. Para conhecer as limitações de consultas do Cloud Datastore, consulte a página Consultas do Datastore.

Consultas com JDOQL

A JDO inclui uma linguagem de consulta para recuperar objetos que atendem a um conjunto de critérios. Essa linguagem, chamada JDOQL (em inglês), refere-se diretamente às classes e campos de dados JDO e inclui a verificação de tipos para parâmetros e resultados de consultas. A JDOQL é semelhante à SQL, mas é mais adequada para bancos de dados orientados a objetos, como o App Engine Datastore. A implementação da API JDO do App Engine não é compatível com consultas SQL diretamente.

A interface Query da JDO é compatível com vários estilos de chamada. Para especificar uma consulta completa em uma string, use a sintaxe de string da JDOQL. Caso queira especificar algumas ou todas as partes da consulta, chame métodos no objeto Query. Veja no exemplo a seguir o estilo de método de chamada, com um filtro e uma ordem de classificação, usando a substituição de parâmetros para o valor utilizado no filtro. Os valores de argumento transmitidos ao método Query do objeto execute() são substituídos na consulta na ordem especificada:

import java.util.List;
import javax.jdo.Query;

// ...

Query q = pm.newQuery(Person.class);
q.setFilter("lastName == lastNameParam");
q.setOrdering("height desc");
q.declareParameters("String lastNameParam");

try {
  List<Person> results = (List<Person>) q.execute("Smith");
  if (!results.isEmpty()) {
    for (Person p : results) {
      // Process result p
    }
  } else {
    // Handle "no results" case
  }
} finally {
  q.closeAll();
}

Esta é a mesma consulta, usando a sintaxe da string:

Query q = pm.newQuery("select from Person " +
                      "where lastName == lastNameParam " +
                      "parameters String lastNameParam " +
                      "order by height desc");

List<Person> results = (List<Person>) q.execute("Smith");

Você pode misturar os estilos de definição da consulta. Por exemplo:

Query q = pm.newQuery(Person.class,
                      "lastName == lastNameParam order by height desc");
q.declareParameters("String lastNameParam");

List<Person> results = (List<Person>) q.execute("Smith");

Você pode reutilizar uma única instância de Query com valores diferentes substituídos pelos parâmetros. Para isso, chame o método execute() várias vezes. Cada chamada realizará a consulta e retornará os resultados como um conjunto.

A sintaxe de string da JDOQL é compatível com a especificação literal de valores numéricos e de string. Todos os outros tipos de valor precisam usar a substituição de parâmetros. Os literais na string de consulta podem ser incluídos entre aspas simples (') ou duplas ("). Veja aqui um exemplo que usa uma string literal:

Query q = pm.newQuery(Person.class,
                      "lastName == 'Smith' order by height desc");

Filtros

Um filtro de propriedade especifica:

  • um nome de propriedade;
  • um operador de comparação;
  • um valor de propriedade.
Por exemplo:

Query q = pm.newQuery(Person.class);
q.setFilter("height <= maxHeight");

O aplicativo precisa fornecer o valor da propriedade. Ele não pode referir-se a outras propriedades nem ser calculado em relação a elas. Uma entidade satisfaz ao filtro se tiver uma propriedade com o nome determinado com valor igual ao especificado no filtro, da maneira descrita pelo operador de comparação.

O operador de comparação pode ser qualquer um destes:

Operador Significado
== Igual a
< Menor que
<= Menor que ou igual a
> Maior que
>= Maior que ou igual a
!= Diferente de

Conforme descrito na página principal Consultas, uma única consulta não pode usar filtros de desigualdade (<, <=, >, >=, !=) em mais de uma propriedade. São permitidos filtros de desigualdade múltiplos na mesma propriedade, como a consulta de um intervalo de valores. Os filtros contains(), que correspondem a filtros IN em SQL, são compatíveis com a seguinte sintaxe:

// Query for all persons with lastName equal to Smith or Jones
Query q = pm.newQuery(Person.class, ":p.contains(lastName)");
q.execute(Arrays.asList("Smith", "Jones"));

O operador "diferente de" (!=) realiza, na verdade, duas consultas: uma em que todos os outros filtros não são alterados e o filtro de desigualdade é substituído por um filtro Menor que (<) e outra em que ele é substituído por um filtro Maior que (>). Em seguida, os resultados são mesclados na ordem. Uma consulta não pode ter mais que um filtro "diferente de", e a que tiver um não poderá ter outros filtros de desigualdade.

O operador contains() também executa várias consultas: uma para cada item na lista especificada, com todos os outros filtros inalterados e o filtro contains() substituído por um filtro de igualdade (==). Os resultados são mesclados na ordem dos itens na lista. Se uma consulta tiver mais de um filtro contains(), ela será executada como várias consultas, uma para cada combinação possível de valores nas listas contains().

Uma única consulta contendo os operadores "diferente de" (!=) ou contains() é limitada a 30 subconsultas.

Para mais informações sobre como as consultas != e contains() são convertidas em várias consultas em um framework JDO/JPA, consulte o artigo Consultas com filtros != e IN (em inglês).

Na sintaxe de string da JDOQL, você pode separar vários filtros com os operadores && ("AND" lógico) e || ("OR" lógico):

q.setFilter("lastName == 'Smith' && height < maxHeight");

O operador de negação ("NOT" lógico) não é compatível. Lembre-se também de que o operador || só pode ser empregado quando os filtros separados por ele tiverem o mesmo nome de propriedade (ou seja, quando puderem ser combinados em um único filtro contains()):

// Legal: all filters separated by || are on the same property
Query q = pm.newQuery(Person.class,
                      "(lastName == 'Smith' || lastName == 'Jones')" +
                      " && firstName == 'Harold'");

// Not legal: filters separated by || are on different properties
Query q = pm.newQuery(Person.class,
                      "lastName == 'Smith' || firstName == 'Harold'");

Ordens de classificação

A ordem de classificação de uma consulta especifica:

  • um nome de propriedade;
  • uma direção de classificação (crescente ou decrescente).

Por exemplo:

Por exemplo:

// Order alphabetically by last name:
Query q = pm.newQuery(Person.class);
q.setOrdering("lastName asc");

// Order by height, tallest to shortest:
Query q = pm.newQuery(Person.class);
q.setOrdering("height desc");

Se uma consulta incluir várias ordens de classificação, elas serão aplicadas na sequência especificada. O exemplo a seguir classifica primeiro por sobrenome em ordem crescente e depois por altura em ordem decrescente:

Query q = pm.newQuery(Person.class);
q.setOrdering("lastName asc, height desc");

Se nenhuma ordem de classificação for especificada, os resultados serão retornados na ordem em que forem recuperados do Cloud Datastore.

Observação: devido à maneira como o Cloud Datastore executa consultas, se uma consulta especificar filtros de desigualdade em uma propriedade e ordens de classificação em outras propriedades, será preciso ordenar a propriedade usada nos filtros de desigualdade antes das outras.

Intervalos

Uma consulta pode especificar um intervalo de resultados a serem retornados para o aplicativo. O intervalo indica quais resultados no conjunto completo precisam ser o primeiro e último retornados. Os resultados são identificados por seus índices numéricos. 0 indica o primeiro resultado no conjunto. Por exemplo, um intervalo de 5, 10 retorna os resultados do 6º ao 10º:

q.setRange(5, 10);

Observação: o uso de intervalos pode afetar o desempenho, porque o Datastore precisa recuperar e depois descartar todos os resultados que precedem o deslocamento inicial. Por exemplo, uma consulta com o intervalo 5, 10 recupera dez resultados do Datastore, descarta os cinco primeiros e retorna os cinco restantes para o aplicativo.

Consultas com base em chaves

As chaves de entidade podem ser o assunto de um filtro de consulta ou de uma ordem de classificação. O Datastore considera os pares de chave-valor completos para essas consultas, incluindo o caminho do ancestral da entidade, o tipo e a string de nome da chave atribuída pelo aplicativo ou o ID numérico atribuído pelo sistema. Como a chave é única em todas as entidades do sistema, as consultas de chaves facilitam a recuperação de entidades de um determinado tipo em lotes. Um exemplo é um despejo em lote de conteúdos do Datastore. Ao contrário dos intervalos JDOQL, isso funciona de maneira eficaz em qualquer número de entidades.

Na comparação por desigualdade, as chaves são ordenadas pelos seguintes critérios, nesta ordem:

  1. Caminho do ancestral
  2. Tipo de entidade
  3. identificador (nome da chave ou código numérico)

Os elementos do caminho ancestral são comparados de forma similar: por tipo (string) e depois pelo nome da chave ou código numérico. Tipos e nomes de chaves são strings e são ordenados por valor de byte. Já os códigos numéricos são números inteiros e são ordenados numericamente. Se entidades com o mesmo pai e do mesmo tipo usarem uma combinação de strings de nomes de chave e códigos numéricos, aquelas com códigos numéricos precederão as com nomes de chave.

Na JDO, use o campo de chave principal do objeto para referir-se à chave de entidade na consulta. Caso queira usar uma chave como um filtro de consulta, especifique o tipo de parâmetro Key para o método declareParameters(). O exemplo a seguir encontra todas as entidades Person com um determinado alimento favorito, presumindo um relacionamento um para um sem proprietários entre Person e Food:

Food chocolate = /*...*/;

Query q = pm.newQuery(Person.class);
q.setFilter("favoriteFood == favoriteFoodParam");
q.declareParameters(Key.class.getName() + " favoriteFoodParam");

List<Person> chocolateLovers = (List<Person>) q.execute(chocolate.getKey());

Uma consulta apenas de chaves retorna somente as chaves das entidades resultantes em vez das entidades inteiras, o que resulta em latência e custo menores:

Query q = pm.newQuery("select id from " + Person.class.getName());
List<String> ids = (List<String>) q.execute();

Muitas vezes, é mais econômico fazer uma consulta apenas de chaves primeiro e, em seguida, buscar um subconjunto de entidades a partir dos resultados em vez de executar uma consulta geral, que pode buscar mais entidades do que você realmente precisa.

Extensões

Uma extensão da JDO representa todos os objetos de uma determinada classe no Datastore. Para criar essa extensão, transmita a classe desejada ao método getExtent() (em inglês) do Persistence Manager. A interface Extent amplia a interface Iterable (ambos em inglês) para acessar os resultados e os recupera em lotes conforme necessário. Quando terminar de acessar os resultados, chame o método closeAll() (em inglês) da extensão.

No exemplo a seguir, cada objeto Person é iterado no Datastore:

import java.util.Iterator;
import javax.jdo.Extent;

// ...

Extent<Person> extent = pm.getExtent(Person.class, false);
for (Person p : extent) {
  // ...
}
extent.closeAll();

Como excluir entidades por consulta

Se você estiver emitindo uma consulta com o objetivo de excluir todas as entidades que correspondem ao filtro, use o recurso "excluir por consulta" da JDO para economizar um pouco de codificação. No exemplo a seguir, todas as pessoas acima de uma determinada altura são excluídas:

Query q = pm.newQuery(Person.class);
q.setFilter("height > maxHeightParam");
q.declareParameters("int maxHeightParam");
q.deletePersistentAll(maxHeight);

Observe que a única diferença é que chamamos q.deletePersistentAll() em vez de q.execute() (ambos em inglês). Todas as regras e restrições descritas acima para filtros, ordens de classificação e índices se aplicam a consultas, tanto para a seleção quanto para a exclusão do conjunto de resultados. No entanto, assim como se você tivesse excluído essas entidades Person com o comando pm.deletePersistent() (em inglês), todos os filhos dependentes das entidades excluídas pela consulta também serão excluídos. Para saber mais sobre filhos dependentes, veja a página Relacionamentos de entidades na JDO.

Cursores de consulta

Na JDO, use uma extensão e a classe JDOCursorHelper para usar cursores com consultas JDO. Os cursores funcionam ao buscar resultados como uma lista ou usando um iterador. Para receber um cursor, passe a lista de resultados ou o iterador para o método estático JDOCursorHelper.getCursor():

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jdo.Query;
import com.google.appengine.api.datastore.Cursor;
import org.datanucleus.store.appengine.query.JDOCursorHelper;

Query q = pm.newQuery(Person.class);
q.setRange(0, 20);

List<Person> results = (List<Person>) q.execute();
// Use the first 20 results

Cursor cursor = JDOCursorHelper.getCursor(results);
String cursorString = cursor.toWebSafeString();
// Store the cursorString

// ...

// Query q = the same query that produced the cursor
// String cursorString = the string from storage
Cursor cursor = Cursor.fromWebSafeString(cursorString);
Map<String, Object> extensionMap = new HashMap<String, Object>();
extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, cursor);
q.setExtensions(extensionMap);
q.setRange(0, 20);

List<Person> results = (List<Person>) q.execute();
// Use the next 20 results

Para saber mais sobre cursores de consulta, veja a página Consultas do Datastore.

Política de leitura e duração máxima da chamada do Datastore

Use configurações para definir a política de leitura (consistência forte X consistência eventual) e a duração máxima de chamadas ao Datastore para todas as chamadas feitas por uma instância de PersistenceManager. Você também pode substituir essas opções para um objeto Query individual. Observe, no entanto, que não é possível substituir a configuração dessas opções ao buscar entidades por chave.

Quando a consistência eventual é selecionada para uma consulta do Datastore, os índices usados pela consulta para coletar resultados também são acessados com consistência eventual. De vez em quando, as consultas retornam entidades que não correspondem aos critérios, embora isso também ocorra com uma política de leitura fortemente consistente. Se a consulta usar um filtro ancestral, use transações para garantir um conjunto de resultados consistente. Consulte o artigo Isolamento de transações no App Engine para ver mais informações sobre como as entidades e os índices são atualizados.

Para substituir a política de leitura por uma única consulta, chame o método addExtension() (em inglês):

Query q = pm.newQuery(Person.class);
q.addExtension("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");

Os valores possíveis são "EVENTUAL" e "STRONG". O padrão é "STRONG", a não ser que outro tenha sido definido no arquivo de configuração jdoconfig.xml.

Para substituir a duração máxima da chamada ao Datastore por uma única consulta, chame o método setDatastoreReadTimeoutMillis() (em inglês):

q.setDatastoreReadTimeoutMillis(3000);

O valor é um intervalo de tempo expresso em milissegundos.

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Ambiente padrão do App Engine para Java 8