Este documento centra-se na utilização da estrutura de persistência Java Data Objects (JDO) para consultas do App Engine Datastore. Para mais informações gerais acerca das consultas, consulte a página principal Consultas do Datastore.
Uma consulta obtém entidades do Datastore que cumprem um conjunto de condições especificado. A consulta opera em entidades de um determinado tipo; pode especificar filtros nos valores das propriedades, chaves e antecessores das entidades, e pode devolver zero ou mais entidades como resultados. Uma consulta também pode especificar ordens de ordenação para sequenciar os resultados pelos respetivos valores das propriedades. Os resultados incluem todas as entidades que têm, pelo menos, um valor (possivelmente nulo) para cada propriedade com nome nos filtros e nas ordens de ordenação, e cujos valores das propriedades cumprem todos os critérios de filtro especificados. A consulta pode devolver entidades completas, entidades projetadas, ou apenas chaves de entidades.
Uma consulta típica inclui o seguinte:
- Um tipo de entidade ao qual a consulta se aplica
- Zero ou mais filtros com base nos valores das propriedades, nas chaves e nos antecessores das entidades
- Zero ou mais ordens de ordenação para sequenciar os resultados
Nota: para conservar a memória e melhorar o desempenho, uma consulta deve, sempre que possível, especificar um limite para o número de resultados devolvidos.
Nota: o mecanismo de consulta baseado em índice suporta uma vasta gama de consultas e é adequado para a maioria das aplicações. No entanto, não suporta alguns tipos de consultas comuns noutras tecnologias de bases de dados. Em particular, as junções e as consultas agregadas não são suportadas no motor de consultas do Datastore. Consulte a página Consultas do Datastore para ver as limitações das consultas do Datastore.
Consultas com JDOQL
O JDO inclui uma linguagem de consulta para obter objetos que cumprem um conjunto de critérios. Esta linguagem, denominada JDOQL, refere-se diretamente a campos e classes de dados JDO e inclui a verificação de tipos para parâmetros de consulta e resultados. O JDOQL é semelhante ao SQL, mas é mais adequado para bases de dados orientadas para objetos, como o App Engine Datastore. (A implementação da API JDO do App Engine não suporta consultas SQL diretamente.)
A interface JDO
Query
suporta vários estilos de chamadas: pode especificar uma consulta completa numa string, usando a sintaxe de string JDOQL, ou pode especificar algumas ou todas as partes da consulta chamando métodos no objeto Query
. O exemplo
seguinte mostra o estilo de chamada do método, com um filtro e uma ordem de ordenação,
usando a substituição de parâmetros para o valor usado no filtro. Os valores dos argumentos transmitidos ao objeto Query
são substituídos na consulta pela ordem especificada:execute()
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(); }
Aqui está a mesma consulta com a sintaxe de 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");
Pode combinar estes 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");
Pode reutilizar uma única instância de Query
com valores diferentes substituídos pelos parâmetros chamando o método execute()
várias vezes. Cada chamada executa a consulta e devolve os resultados como uma coleção.
A sintaxe de string JDOQL suporta a especificação literal de valores de string e numéricos. Todos os outros tipos de valores têm de usar a substituição de parâmetros. Os literais na string de consulta podem estar entre aspas simples ('
) ou duplas ("
). Segue-se um exemplo que usa um literal de string:
Query q = pm.newQuery(Person.class, "lastName == 'Smith' order by height desc");
Filtros
Um filtro de propriedade especifica
- Um nome da propriedade
- Um operador de comparação
- Um valor da propriedade
Query q = pm.newQuery(Person.class); q.setFilter("height <= maxHeight");
O valor da propriedade tem de ser fornecido pela aplicação. Não pode referir-se nem ser calculado em função de outras propriedades. Uma entidade satisfaz o filtro se tiver uma propriedade do nome indicado cujo valor seja comparado com o valor especificado no filtro da forma descrita pelo operador de comparação.
O operador de comparação pode ser qualquer um dos seguintes:
Operador | Significado |
---|---|
== |
Igual a |
< |
Inferior a |
<= |
Inferior ou igual a |
> |
Superior a |
>= |
Superior 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 vários filtros de desigualdade na mesma propriedade, como consultar um intervalo de valores.) Os filtros contains()
, correspondentes aos filtros IN
em SQL, são suportados através da 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
not-equal (!=
)
executa, na verdade, duas consultas: uma em que todos os outros filtros permanecem inalterados e o filtro
not-equal
é substituído por um filtro
less-than (<
)
e outra em que é substituído por um filtro
greater-than (>
)
. Em seguida, os resultados são unidos por ordem. Uma consulta não pode ter mais do que um
filtro diferente, e uma consulta que tenha um não pode 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 unidos pela ordem dos itens na lista. Se uma consulta tiver mais do que um filtro contains()
, é realizada como várias consultas, uma para cada combinação possível de valores nas listas contains()
.
Uma única consulta que contenha os operadores
not-equal (!=
)
ou contains()
está limitada a um máximo de 30 subconsultas.
Para mais informações sobre como as consultas !=
e contains()
são traduzidas em várias consultas numa framework JDO/JPA, consulte o artigo
Consultas com filtros != e IN.
Na sintaxe de strings JDOQL, pode separar vários filtros com os operadores
&&
(lógico "e") e ||
(lógico "ou"):
q.setFilter("lastName == 'Smith' && height < maxHeight");
A negação (lógica "não") não é suportada. Tenha também em atenção que o operador ||
só pode ser usado quando os filtros que separa têm todos o mesmo nome de propriedade (ou seja, quando podem ser combinados num ú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'");
Ordene encomendas
Uma ordenação de uma consulta especifica
- Um nome da propriedade
- Uma direção de ordenação (ascendente ou descendente)
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 ordenação, estas são aplicadas na sequência especificada. O exemplo seguinte ordena primeiro por apelido em ordem ascendente e, em seguida, por altura em ordem descendente:
Query q = pm.newQuery(Person.class); q.setOrdering("lastName asc, height desc");
Se não forem especificadas ordens de ordenação, os resultados são devolvidos na ordem em que são obtidos a partir do Datastore.
Nota: devido à forma como o Datastore executa as consultas, se uma consulta especificar filtros de desigualdade numa propriedade e ordens de ordenação noutras propriedades, a propriedade usada nos filtros de desigualdade tem de ser ordenada antes das outras propriedades.
Intervalos
Uma consulta pode especificar um intervalo de resultados a devolver à aplicação. O intervalo indica que resultados no conjunto de resultados completo devem ser os primeiros e os últimos devolvidos. Os resultados são identificados pelos respetivos índices numéricos, sendo que 0
indica o primeiro resultado no conjunto. Por exemplo, um intervalo de 5,
10
devolve os resultados do 6.º ao 10.º:
q.setRange(5, 10);
Nota: a utilização de intervalos pode afetar o desempenho, uma vez que o Datastore tem de obter e, em seguida, rejeitar todos os resultados anteriores ao desvio inicial. Por exemplo, uma consulta com um intervalo de 5,
10
obtém dez resultados do Datastore, rejeita os primeiros
cinco e devolve os cinco restantes à aplicação.
Consultas baseadas em chaves
As chaves de entidades podem ser o assunto de um filtro de consulta ou de uma ordem de ordenação. O Datastore considera o valor da chave completo para essas consultas, incluindo o caminho de antepassados da entidade, o tipo e a string do nome da chave atribuída pela aplicação ou o ID numérico atribuído pelo sistema. Uma vez que a chave é única em todas as entidades no sistema, as consultas de chave facilitam a obtenção de entidades de um determinado tipo em lotes, como para uma descarga em lote dos conteúdos do Datastore. Ao contrário dos intervalos JDOQL, isto funciona de forma eficiente para qualquer número de entidades.
Quando faz a comparação para verificar a desigualdade, as chaves são ordenadas pelos seguintes critérios, por ordem:
- Caminho do antepassado
- Tipo de entidade
- Identificador (nome da chave ou ID numérico)
Os elementos do caminho de antepassados são comparados de forma semelhante: por tipo (string) e, em seguida, pelo nome da chave ou ID numérico. Os tipos e os nomes das chaves são strings e são ordenados por valor de byte; os IDs numéricos são números inteiros e são ordenados numericamente. Se as entidades com o mesmo elemento principal e tipo usarem uma combinação de strings de nomes de chaves e IDs numéricos, as entidades com IDs numéricos precedem as entidades com nomes de chaves.
No JDO, refere-se à chave da entidade na consulta através do campo de chave principal do objeto. Para usar uma chave como filtro de consulta, especifique o tipo de parâmetro
Key
para o método
declareParameters()
.
O seguinte encontra todas as entidades Person
com um determinado alimento favorito, partindo do princípio de que existe uma relação individual não pertencente 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 com chaves devolve apenas as chaves das entidades de resultados, em vez das próprias entidades, com uma latência e um custo inferiores aos da obtenção de entidades inteiras:
Query q = pm.newQuery("select id from " + Person.class.getName()); List<String> ids = (List<String>) q.execute();
Geralmente, é mais económico fazer primeiro uma consulta apenas de chaves e, em seguida, obter um subconjunto de entidades dos resultados, em vez de executar uma consulta geral que pode obter mais entidades do que as que realmente precisa.
Extensões
Uma extensão JDO representa todos os objetos no Datastore de uma classe específica. Crie-o transmitindo a classe pretendida ao método
getExtent()
do PersistenceManager. A interface
Extent
estende a interface
Iterable
para aceder aos resultados e obtê-los em lotes, conforme necessário. Quando terminar de aceder aos resultados, chama o método closeAll()
do alcance.
O exemplo seguinte itera sobre todos os objetos Person
no
Datastore:
import java.util.Iterator; import javax.jdo.Extent; // ... Extent<Person> extent = pm.getExtent(Person.class, false); for (Person p : extent) { // ... } extent.closeAll();
Eliminar entidades por consulta
Se estiver a emitir uma consulta com o objetivo de eliminar todas as entidades que correspondam ao filtro de consulta, pode poupar algum tempo de programação usando a funcionalidade "eliminar por consulta" do JDO. O seguinte comando elimina todas as pessoas com uma determinada altura:
Query q = pm.newQuery(Person.class); q.setFilter("height > maxHeightParam"); q.declareParameters("int maxHeightParam"); q.deletePersistentAll(maxHeight);
Vai reparar que a única diferença aqui é que estamos a chamar
q.deletePersistentAll()
em vez de
q.execute()
.
Todas as regras e restrições descritas acima para filtros, ordens de ordenação e
índices aplicam-se a consultas, quer esteja a selecionar ou a eliminar o conjunto de resultados.
No entanto, tal como se tivesse eliminado estas Person
entidades com
pm.deletePersistent()
,
todos os elementos secundários dependentes das entidades eliminadas pela consulta também vão ser
eliminados. Para mais informações sobre crianças dependentes, consulte a página
Relações de entidades no JDO.
Cursores de consulta
No JDO, pode usar uma extensão e a classe JDOCursorHelper
para usar cursores com consultas JDO. Os cursores funcionam quando obtém resultados como uma lista ou usa um iterador. Para obter um cursor, transmite a lista de resultados ou o iterador
ao 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 mais informações sobre os cursores de consulta, consulte a página Consultas do Datastore.
Política de leitura do armazenamento de dados e prazo da chamada
Pode definir a política de leitura (consistência forte vs. consistência eventual) e o prazo de chamada do Datastore para todas as chamadas feitas por uma instância do PersistenceManager
através da configuração. Também pode
substituir estas opções para um Query
objeto individual. (Tenha em atenção, no entanto, que não existe forma de substituir a configuração destas opções quando obtém entidades por chave.)
Quando a consistência eventual é selecionada para uma consulta do Datastore, os índices que a consulta usa para recolher resultados também são acedidos com consistência eventual. As consultas devolvem ocasionalmente entidades que não correspondem aos critérios de consulta, embora isto também seja verdade com uma política de leitura fortemente consistente. (Se a consulta usar um filtro de hierarquia, pode usar transações para garantir um conjunto de resultados consistente.)
Para substituir a política de leitura de uma única consulta, chame o respetivo método
addExtension()
:
Query q = pm.newQuery(Person.class); q.addExtension("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");
Os valores possíveis são "EVENTUAL"
e "STRONG"
. A predefinição é "STRONG"
, salvo indicação em contrário no ficheiro de configuração jdoconfig.xml
.
Para substituir o prazo da chamada ao armazenamento de dados para uma única consulta, chame o respetivo método
setDatastoreReadTimeoutMillis()
:
q.setDatastoreReadTimeoutMillis(3000);
O valor é um intervalo de tempo expresso em milissegundos.