Consultas dinâmicas e typesafe em JPA 2.0

Como a Criteria API constrói consultas dinâmicas e reduz as falhas de tempo de execução

Uma consulta de objetos persistentes Java é typesafe™ se um compilador puder verificá-la quanto à correção sintática. A versão 2.0 da API de persistência Java (JPA) introduz a Criteria API, que oferece o poder das consultas typesafe para aplicativos Java pela primeira vez e oferece um mecanismo para construir consultas de forma dinâmica no tempo de execução. Este artigo descreve como escrever consultas dinâmicas, typesafe utilizando a Criteria API e a API de metamodelo diretamente associada.

Pinaki Poddar, Senior Software Engineer, IBM  

Pinaki PoddarPinaki Poddar works in middleware technology with an emphasis on object persistence. He is a member of the Expert Group for the Java Persistence API (JSR 317) specification and a committer for the Apache OpenJPA project. In a past life, he contributed to the building of component-oriented integration middleware for a global investment bank and a medical image-processing platform for the healthcare industry. For his doctoral thesis, he developed an indigenous, neural-network-based automatic speech-recognition system.



14/Mai/2014

A comunidade de desenvolvedores Java deu as boas-vindas à JPA desde sua introdução em 2006. A próxima atualização importante da especificação — versão 2.0 (JSR 317) — será concluída no final de 2009 (consulte Recursos). Um dos principais recursos introduzidos na JPA 2.0 é a Criteria API, que traz um recurso exclusivo para a linguagem Java: uma forma de desenvolver consultas que um compilador Java possa verificar quanto à correção no momento da compilação. A Criteria API também inclui mecanismos para construir consultas de forma dinâmica no tempo de execução.

Este artigo apresenta a Criteria API e o conceito de metamodelo diretamente associado. Você saberá como utilizar a Criteria API para desenvolver consultas que um compilador Java possa verificar quanto à correção para reduzir erros de tempo de execução, em contraste com as consultas baseadas em cadeias de caracteres da Java Persistence Query Language (JPQL). E através de consultas de exemplo que utilizam funções de banco de dados ou comparam uma instância de modelo, demonstrarei o poder agregado da mecânica programática de construção de consulta comparada com consultas JPQL que utilizam uma gramática predefinida. O artigo supõe que você tenha familiaridade básica da programação em linguagem Java e uso comum de JPA como EntityManagerFactory ou EntityManager.

O que há de errado com a consulta JPQL?

A JPA 1.0 introduziu a JPQL, uma linguagem poderosa de consulta considerada como a principal razão da popularidade da JPA. Entretanto, a JPQL — sendo uma linguagem de consulta baseada em cadeia de caracteres com uma gramática definida — tem algumas limitações. Para compreender uma das principais limitações, considere o simples fragmento de código na Listagem 1, que executa uma consulta JPQL para selecionar a lista de Pessoas maiores de 20 anos:

Listagem 1. Uma consulta simples (e errada) JPQL
EntityManager em = ...;
String jpql = "select p from Person where p.age > 20";
Query query = em.createQuery(jpql);
List result = query.getResultList();

Esse exemplo básico mostra os seguintes aspectos principais do modelo de execução de consulta na JPA 1.0:

  • Uma consulta JPQL é especificada como uma Cadeia de caractere (linha 2).
  • O EntityManager é a fábrica que constrói uma instância executável de consulta dada uma cadeia de caractere JPQL (linha 3).
  • O resultado da execução de consulta consiste nos elementos de uma java.util.List não tipificado.

Mas este exemplo simples tem um erro sério. Efetivamente, o código compilará tranquilamente, mas falhará no tempo de execução porque a cadeia de caractere de consulta JPQL está sintaticamente incorreta. A sintaxe correta para a segunda linha da Listagem 1 é:

String jpql = "select p from Person p where p.age > 20";

Infelizmente, o compilador Java não tem como detectar tal erro. O erro será encontrado no tempo de execução, na linha 3 ou na linha 4 (dependendo se o provedor de JPA analisa uma cadeia de caracteres JPQL de acordo com a gramática JPQL durante a construção ou execução da consulta).

Como uma consulta typesafe ajuda?

Uma das principais vantagens da Criteria API é que ela proíbe a construção de consultas sintaticamente incorretas. A Listagem 2 reescreve a consulta JPQL da Listagem 1 utilizando a interface CriteriaQuery:

Listagem 2. Etapas básicas de escrita de uma CriteriaQuery
EntityManager em = ...
QueryBuilder qb = em.getQueryBuilder();
CriteriaQuery<Person> c = qb.createQuery(Person.class);
Root<Person> p = c.from(Person.class);
Predicate condition = qb.gt(p.get(Person_.age), 20);
c.where(condition);
TypedQuery<Person> q = em.createQuery(c); 
List<Person> result = q.getResultList();

Listagem 2 ilustra as estruturas principais da Criteria API e demonstra seu uso básico:

  • A linha 1 obtém uma instância EntityManager através de um de vários meios.
  • Na linha 2, o EntityManager cria uma instância do QueryBuilder. QueryBuilder é o factory para o CriteriaQuery.
  • Na linha 3, o factory QueryBuilder constrói uma instância do CriteriaQuery. Um CriteriaQuery é genericamente tipificado. O argumento de tipo genérico declara o tipo de resultado que essa CriteriaQuery retornará na execução. É possível fornecer vários tipos de argumentos de tipo de resultado — desde uma entidade persistente como a Person.class até uma de forma mais livre como a Object[]— ao construir uma CriteriaQuery.
  • Na linha 4, as expressões de consulta são definidas na instância CriteriaQuery. As expressões de consulta são as unidades de núcleo ou nós montados em uma árvore para especificar uma CriteriaQuery. A figura 1 mostra a hierarquia das expressões de consulta definidas na Criteria API:

    Figura 1. Hierarquia de interface das expressões de consulta

    Para começar, a CriteriaQuery é definida para consultar a partir de Person.class. Como resultado, uma instância Root<Person>p é retornada. Root é uma expressão de consulta que denota a extensão de uma entidade persistente. Root<T> essencialmente diz: "Avalie esta consulta através de todas as instâncias do tipo T." É semelhante à cláusula FROM de uma consulta JPQL ou SQL. Observe também que Root<Person> é tipificado genericamente. (Na verdade, toda expressão é.) O argumento de tipo é o tipo de valor que a expressão avalia. Portanto, Root<Person> denota uma expressão que avalia para Person.class.

  • A linha 5 constrói um Predicado. Um predicado é outra forma comum de expressão de consulta que avalia quanto a verdadeiro ou falso. Um predicado é construído pelo QueryBuilder, que é o factory não apenas para a CriteriaQuery, como também para as expressões de consulta. O QueryBuilder tem métodos de API para construir todos os tipos de expressões de consulta suportados na gramática tradicional JPQL, e mais alguns. Na listagem 2, o QueryBuilder é usado para construir uma expressão que avalia se o valor de seu primeiro argumento de expressão é numericamente maior que o valor do segundo argumento. A assinatura do método é:

    Predicate gt(Expression<? extends Number> x, Number y);

    Essa assinatura do método é um ótimo exemplo de como uma linguagem fortemente tipificado como a linguagem Java pode ser criteriosamente usada para definir uma API que permita expressar o que é correto e proibir o que não é. A assinatura do método especifica que é possível comparar uma expressão cujo valor é um Número somente com outro Número (e não, por exemplo, com uma Cadeia de caracteres):

    Predicate condition = qb.gt(p.get(Person_.age), 20);

    Mas há mais na linha 5. Observe o primeiro argumento de entrada qb.gt() do método: p.get(Person_.age), onde p é a expressão Root<Person> obtida anteriormente. p.get(Person_.age) é uma expressão de caminho. Uma expressão de caminho é o resultado da navegação de uma expressão de raiz via um ou mais atributo(s) persistente(s). Portanto, p.get(Person_.age) denota uma expressão de caminho ao navegar da expressão de raiz p pelo atributo de age da Person. Você pode imaginar qual é a Person_.age. Por enquanto, suponha que é uma forma de denotar o atributo de age da Person. Eu elaborarei sobre o significado de Person_.age quando discutir sobre a nova API metamodelo introduzida na JPA 2.0.

    Como mencionei anteriormente, cada expressão de consulta é genericamente tipificada para denotar o tipo de valor que a expressão avalia. A expressão de caminho p.get(Person_.age) avalia um Número inteiro se o atributo age na Person.class for declarado como do tipo Integer (ou int). Como a segurança do tipo inerente à API, o compilador em si aumentará um erro para uma comparação sem sentido, como:

    Predicate condition = qb.gt(p.get(Person_.age, "xyz"));
  • A linha 6 define o predicado na CriteriaQuery como a cláusula WHERE.
  • Na linha 7, o EntityManager cria uma consulta executável dada uma entrada CriteriaQuery. Isso é semelhante à construção de uma consulta executável dada uma cadeia de caractere JPQL como entrada. Mas porque a entrada CriteriaQuery carrega tipo mais rico de informações, o resultado é uma TypedQuery que é uma extensão da familiar javax.persistence.Query. A TypedQuery, como o próprio nome sugere, conhece o tipo e retorna como resultado de sua execução. Ela é definida como:

    public interface TypedQuery<T> extends Query {
                 List<T> getResultList();
    }

    Como oposta à super interface correspondente não tipificada:

    public interface Query {
    List getResultList();

    Naturalmente, o resultado da TypedQuery tem o mesmo tipo de Person.class especificado durante a construção da entrada CriteriaQuery por um QueryBuilder (linha 3).

  • Na linha 8, as informações do tipo carregado por toda parte mostram sua vantagem quando a consulta é finalmente executada para obter uma lista de resultados. O resultado é uma lista tipificada de Persons que poupa o desenvolvedor do trabalho de um cast extra (e frequentemente feio) (além de minimizar o risco de erro de ClassCastException no tempo de execução) ao iterar através dos elementos resultantes.

Para resumir os aspectos básicos do exemplo simples na Listagem 2:

  • CriteriaQuery é uma árvore de nós de expressão de consulta utilizada para especificar cláusulas de consulta como FROM, WHERE e ORDER BY em uma linguagem tradicional de consulta baseada em cadeia de caracteres. A figura 2 mostra as cláusulas relacionadas a uma consulta:
    Figura 2. CriteriaQuery encapsula as cláusulas de uma consulta tradicional
  • As expressões de consulta são tipificadas genericamente. Alguns exemplos típicos são:
    • Root<T>, equivalente à cláusula DE.
    • Predicado, que avalia um valor booleano de verdadeiro ou falso. (Na verdade, é declarada como Predicado da interface estende Expressão <Booleana>.)
    • Path<T>, que denota um atributo persistente navegado a partir de uma expressão de Raiz<?>. Root<T> é um Path<T> especial sem pai.
  • QueryBuilder é o factory para o CriteriaQuery e expressões de consulta de todos os tipos.
  • CriteriaQuery é transferida para uma consulta executável com a informação do seu tipo preservada para que os elementos da lista selecionada possam ser acessados sem qualquer moldagem do tempo de execução.

Metamodelo de um domínio persistente

A discussão da Listagem 2 aponta uma construção incomum: Person_.age, que é uma designação para o atributo persistente de age da Person. A listagem 2 utiliza Person_.age para formar uma expressão de caminho que navega de uma expressão Root<Person>p por p.get(Person_.age). Person_.age é um campo público estático na classe Person_ e Person_ é a classe de metamodelo canônica, estática, instanciada correspondente à classe original de entidade Person.

Uma classe de metamodelo descreve as meta-informações de uma classe persistente. Uma classe de metamodelo é canônica se a classe descrever as meta-informações de uma entidade persistente da forma exata estipulada pela especificação JPA 2.0. Uma classe de metamodelo canônica é estática no mesmo sentido que todos os seus membros variáveis são declarados estáticos (e públicos). Person_.age é um desses membros variáveis estáticos. A classe canônica é instanciada gerando um Person_.java concreto no nível de código de origem no momento do desenvolvimento. Através de tal instanciação, é possível referir-se aos atributos persistentes de Person no momento da compilação e não no tempo de execução, de forma fortemente tipificada.

Essa classe Pessoa_ metamodelo é um meio alternativo de fazer referência à meta informação de Person. Essa alternativa é semelhante à muito usada (alguns diriam até, abusada) API Java Reflection, mas com uma principal diferença conceitual. É possível usar a reflexão para obter meta informação sobre uma instância java.lang.Class, mas a meta informação Person.class não pode ser referida de forma que um compilador possa verificar. Por exemplo, utilizando reflexão, se faria referência ao campo chamado age na Person.class com:

Field field = Person.class.getField("age");

Entretanto, essa técnica carrega o fardo de uma limitação semelhante à observada no caso da consulta JPQL baseada em cadeia de caracteres da Listagem 1. O compilador fica feliz com essa parte de código, mas não pode verificar se ele funcionará. O código pode falhar no momento da execução se incluir até mesmo um simples erro de digitação. A reflexão não funcionará para o que a consulta typesafe da JPA 2.0 pretende alcançar.

Uma API de consulta typesafe deve ativar seu código para referir-se ao atributo persistente chamado age em uma classe Person de forma que um compilador possa verificar no momento da compilação. A solução oferecida pela JPA 2.0 é a capacidade de instanciar uma classe metamodelo chamada Person_, que corresponda à Person expondo os mesmos atributos persistentes estatisticamente.

Qualquer discussão sobre meta ou meta-meta informações normalmente causa sonolência. Portanto, apresentarei um exemplo concreto de uma classe metamodelo para uma classe de entidade familiar Plain Old Java Object (POJO)— domain.Person, demonstrada na Listagem 3:

Listagem 3. Entidade persistente simples
package domain;
@Entity
public class Person {
  @Id
  private long ssn;
  private string name;
  private int age;

  // public gettter/setter methods
  public String getName() {...}
}

Esta é uma definição típica de um POJO, com anotações — como @Entity ou @Id — que habilitam um provedor JPA a gerenciar instâncias dessa classe como entidades persistentes.

A classe canônica metamodelo estática correspondente de domain.Person é demonstrada na Listagem 4:

Listagem 4. Metamodelo canônica para uma entidade simples
package domain;
import javax.persistence.metamodel.SingularAttribute;

@javax.persistence.metamodel.StaticMetamodel(domain.Person.class)

public class Person_ {
  public static volatile SingularAttribute<Person,Long> ssn;
  public static volatile SingularAttribute<Person,String> name;
  public static volatile SingularAttribute<Person,Integer> age;
}

A classe metamodelo declara cada atributo persistente da entidade original domain.Person como um campo público estático do tipo SingularAttribute<Person,?>. Utilizando essa classe metamodelo Person_ , posso me referir ao atributo persistente do domain.Person chamado idade— não via API de Reflexão, mas como referência direta ao campo estático Person_.age— no momento da compilação. O compilador pode então forçar a verificação de tipo com base no tipo declarado de atributo chamado idade. Já citei um exemplo de tal restrição: QueryBuilder.gt(p.get(Person_.age), "xyz") causará um erro do compilador porque ele pode determinar a partir da assinatura de QueryBuilder.gt(..) e tipo de Person_.age que a idade da pessoa é um campo numérico e não pode ser comparado com uma Cadeia de caracteres.

Alguns outros pontos a observar são:

  • O campo metamodelo Person_.age é declarado como sendo do tipo javax.persistence.metamodel.SingularAttribute. SingularAttribute é uma das interfaces definidas na API metamodelo JPA, a qual descreverei na próxima seção. Os argumentos do tipo genérico de uma SingularAttribute<Person, Integer> denotam a classe que declara o atributo persistente original e o tipo de atributo persistente em si.
  • A classe metamodelo é anotada como @StaticMetamodel(domain.Person.class) para designá-la como uma classe metamodelo correspondente à entidade original persistente domain.Person.

A API metamodelo

Eu defini uma classe metamodelo como uma descrição de uma classe de entidade persistente. Exatamente como a API de Reflexão precisa de outras interfaces — como java.lang.reflect.Field ou java.lang.reflect.Method — para descrever os constituintes da java.lang.Class, a API metamodelo JPA também precisa de outras interfaces, como SingularAttribute, PluralAttribute, para descrever os tipos de uma classe metamodelo e seus atributos.

A figura 3 mostra as interfaces definidas na API metamodelo para descrever tipos:

Figura 3. Hierarquia de interface para tipos persistentes na API metamodelo

A figura 4 mostra as interfaces definidas na API metamodelo para descrever atributos:

Figura 4. Hierarquia de interface para atributos persistentes na API metamodelo

As interfaces da API metamodelo JPA são mais especializadas do que as da API de Reflexão Java. Sua melhor distinção é necessária para expressar as ricas meta-informações sobre persistência. Por exemplo, a API de Reflexão Java representa todos os tipos Java java.lang.Class. Isto é, nenhuma distinção especial é feita via definições separadas entre conceitos como classe, classe abstrata e uma interface. É claro que é possível perguntar a uma Class se ela é uma interface ou se é abstrata —, mas isso não é o mesmo que representar o conceito de interface de forma diferente de uma classe abstrata via duas definições separadas.

A API de Reflexão Java foi introduzida na concepção da linguagem Java (e foi um conceito pioneiro na época para uma linguagem comum de programação de finalidade geral), mas a conscientização sobre o uso e o poder dos sistemas fortemente tipificados progrediu ao longo dos anos. A API metamodelo JPA utiliza esse poder para apresentar forte tipificação para entidades persistentes. Por exemplo, as entidades persistentes são semanticamente diferenciadas como MappedSuperClass, Entidade e Embeddable. Antes da JPA 2.0, essa distinção semântica era representada através de anotações correspondentes no nível da classe na definição de classe persistente. O metamodelo JPA descreve três interfaces separadas — MappedSuperclassType, EntityType e EmbeddableType— no pacote javax.persistence.metamodel para levar o foco sobre as especialidades semânticas. De forma semelhante, os atributos persistentes são diferenciados no nível da definição de tipo através de interfaces como SingularAttribute, CollectionAttribute e MapAttribute.

Com exceção da descrição estética, essas interfaces metamodelo especializadas têm vantagens práticas que ajudam a construir consultas typesafe e a reduzir a chance de erros no tempo de execução. Algumas dessas vantagens foram mostradas nos exemplos anteriores, e mais será mostrado quando eu descrever exemplos de junções utilizando a CriteriaQuery.

Escopo de tempo de execução

Em termos gerais, é possível traçar alguns paralelos entre interfaces tradicionais da API de Reflexão Java e as interfaces javax.persistence.metamodel especializadas para descrever a persistência de metadados. Para estender a analogia, um conceito equivalente de escopo de tempo de execução é necessário para interfaces metamodelo. As instâncias java.lang.Class têm o escopo definido pelo java.lang.ClassLoader no tempo de execução. Um conjunto de instâncias de classe Java que fazem referência uma à outra deve ser definido sob o escopo de um ClassLoader. Os limites definidos são severos ou fechados no sentido de que se uma classe A definida sob o escopo de um ClassLoader L tentar fazer referência a uma classe B, que não esteja sob o escopo do ClassLoaderL, o resultado é um temido ClassNotFoundException ou NoClassDef FoundError (e normalmente noites em claro para um desenvolvedor ou implementador para ambientes com múltiplos ClassLoaders).

Essa noção de escopo de tempo de execução como um conjunto restrito de classes mutuamente referenciadas é capturado na JPA 1.0 como uma unidade de persistência. O escopo de uma unidade de persistência em termos de suas entidades persistentes é enumerado na cláusula <classe> de um arquivo META-INF/persistence.xml. Na JPA 2.0, o escopo é disponibilizado ao desenvolvedor no tempo de execução através da interface javax.persistence.metamodel.Metamodel. A interface metamodelo é a portadora de todas as entidades persistentes conhecidas para uma unidade específica de persistência, conforme ilustrado na Figura 5:

Figura 5. Interface metamodelo é que contém os tipos em uma unidade de persistência

Essa interface deixa os elementos do metamodelo serem acessados por sua classe de entidade persistente correspondente. Por exemplo, para obter uma referência para os metadados persistentes para uma entidade persistente Pessoa, é possível escrever:

EntityManagerFactory emf = ...;
Metamodel metamodel = emf.getMetamodel();
EntityType<Person> pClass = metamodel.entity(Person.class);

Isso é análogo, com estilo e idiomas levemente diferentes, para obter uma Classe por seu nome via ClassLoader:

ClassLoader classloader =  Thread.currentThread().getContextClassLoader();
Class<?> clazz = classloader.loadClass("domain.Person");

EntityType<Person> pode ser navegado no tempo de execução para obter os atributos persistentes declarados na entidade Pessoa. Se o aplicativo invoca um método na pClass como pClass.getSingularAttribute("age", Integer.class), ele retornará uma instância SingularAttribute<Person, Integer> que é efetivamente a mesma que o membro estático Person_.age da classe metamodelo canônica instanciada. Essencialmente, o atributo persistente a que o aplicativo pode se referir no tempo de execução via a API metamodelo é disponibilizado para um compilador Java por instanciação da classe metamodelo estática canônica Person_ .

Com exceção da resolução de uma entidade persistente para seus elementos metamodelo correspondentes, a API metamodelo também permite acesso a todas as classes metamodelo conhecidas (Metamodel.getManagedTypes()) ou acesso a uma classe metamodelo através de suas informações específicas de persistência — por exemplo embeddable(Address.class), que retorna uma instância EmbeddableType<Address> que é uma subinterface do ManagedType<>.

Na JPA, as meta-informações sobre um POJO são adicionalmente atribuídas com meta informações específicas persistentes — como se uma classe é integrada ou quais campos são utilizados como chave primária — com anotações no nível do código de origem (ou descritores XML). As meta-informações persistentes entram em duas amplas categorias: para persistência (como @Entity) e mapeamento (como @Table). Na JPA 2.0, o metamodelo captura os metadados apenas para anotações de persistência —não para anotação de mapeamento. Portanto, com a versão atual da API metamodelo, é possível saber quais campos são persistentes, mas não é possível descobrir para quais colunas do banco de dados elas são mapeadas.

Canônica x não-canônica

Embora uma especificação JPA 2.0 estipule a forma exata de uma classe metamodelo estática canônica (inclusive nome totalmente qualificado da classe metamodelo e os nomes de seus campos estáticos), é plenamente possível também para um aplicativo escrever essas classes metamodelo. Se o desenvolvedor do aplicativo escrever as classes metamodelo, elas são chamadas de metamodelo não-canônicas. Atualmente, a especificação para metamodelo não-canônico não é muito detalhada e o suporte para metamodelo não-canônico pode não ser portável entre os provedores de JPA. Você pode ter notado que os campos públicos estáticos são apenas declarados no metamodelo canônico, mas não é inicializado. A declaração possibilita fazer referência a esses campos durante o desenvolvimento de uma CriteriaQuery. Mas eles precisam receber um valor para serem úteis no tempo de execução. Embora seja responsabilidade do provedor JPA atribuir valores a esses campos para metamodelo canônico, não é estendida garantia semelhante para metamodelo não-canônico. Os aplicativos que utilizam metamodelo não-canônico devem depender de mecanismos específicos do fornecedor ou projetar sua própria mecânica para inicializar os valores de campo para atributos de metamodelo no tempo de execução.

Geração de código e usabilidade

A geração automática de código de origem normalmente causa desconfiança. O caso de código de origem gerado para metamodelo cônico traz algumas preocupações. As classes geradas são utilizadas durante do desenvolvimento e outras partes do código que constroem a CriteriaQuery fazem referência direta a elas no momento da compilação, deixando algumas dúvidas quanto à usabilidade:

  • Os arquivos de código de origem deveriam ser gerados no mesmo diretório da fonte original, em um diretório separado ou relacionado ao diretório de saída?
  • Os arquivos de código fonte deveriam ser verificados em um sistema de gerenciamento de configuração controlado por versão?
  • Como a correspondência entre uma definição original de entidade Pessoa e seu metamodelo canônico Person_ deve ser mantida? Por exemplo, e se Person.java for editado para adicionar um outro atributo persistente ou refatorado para renomear um atributo persistente?

As respostas a essas perguntas não são definitivas até o momento.

Processamento de anotação e geração de metamodelo

Naturalmente, se você tiver muitas entidades persistentes, não ficará inclinado a escrever classes metamodelo. Espera-se que o provedor de persistência gere essas classes metamodelo para você. A especificação não ordena tal instalação ou a mecânica de geração, mas um entendimento implícito entre os provedores JPA é que eles gerarão o metamodelo canônico utilizando a instalação de Processador de Anotação integrada no compilador Java 6. O Apache OpenJPA oferece um utilitário para gerar essas classes metamodelo implicitamente ao compilar o código de origem para entidades persistentes ou invocando explicitamente um script. Antes do Java 6, uma ferramenta de processador de anotação chamada apt era disponibilizada e muito utilizada — mas com o Java 6, o acoplamento entre o compilador e o Processador de Anotação é definido como parte do padrão.

O processo de geração dessas classes metamodelo no OpenJPA como seu provedor de persistência é simples como compilar a entidade POJO com as bibliotecas de classe OpenJPA no caminho de classe do compilador:

$ javac domain/Person.java

A classe metamodelo canônica Person_ será gerada, escrita no mesmo diretório de origem que Person.java e compilada como efeito colateral dessa compilação.


Escrevendo consultas de forma typesafe

Até aqui, estabeleci os componentes da CriteriaQuery e suas classes metamodelo associadas. Agora mostrarei como desenvolver algumas consultas com a Criteria API.

Expressões funcionais

As expressões funcionais aplicam uma função a um ou mais argumentos de entrada para criar uma nova expressão. O tipo de expressão funcional depende da natureza da função e tipo de seus argumentos. Os argumentos de entrada em si podem ser expressões ou valores literais. As regras de verificação de tipo do compilador, juntamente com a assinatura da API regulam o que constitui entrada legítima.

Considere uma expressão de argumento único que aplica média em sua expressão de entrada. A listagem 5 mostra a CriteriaQuery para selecionar o saldo médio de todas as Contas:

Listagem 5. Expressão funcional na CriteriaQuery
CriteriaQuery<Double> c = cb.createQuery(Double.class);
Root<Account> a = c.from(Account.class);

c.select(cb.avg(a.get(Account_.balance)));

Uma consulta JPQL equivalente seria:

Cadeia de caractere jpql = "select avg(a.balance) from Account a";

Na Listagem 5, o factory QueryBuilder (representado pela variável cb) cria uma expressão avg() e utiliza a expressão na cláusula select() da consulta.

A expressão de consulta é um bloco de construção que pode ser montado para definir o predicado de seleção final para a consulta. O exemplo na Listagem 6 mostra uma expressão de Caminho criada através da navegação para o saldo da Conta, e então a expressão de Caminho é utilizada como expressão de entrada em algumas expressões funcionais binárias —greaterThan() e lessThan()— ambas resultando em uma expressão booleana ou simplesmente um predicado. Os predicados são então combinados via uma operação and() para formar o predicado de seleção final a ser avaliado pela cláusula where() da consulta:

API fluente

Como mostra esse exemplo, os métodos da Criteria API frequentemente retornam o tipo que pode ser usado diretamente em um método relacionado, fornecendo assim um estilo popular de programação conhecido como API fluente.

Listagem 6. Predicado where() na CriteriaQuery
CriteriaQuery<Account> c = cb.createQuery(Account.class);
Root<Account> account = c.from(Account.class);
Path<Integer> balance = account.get(Account_.balance);
c.where(cb.and
       (cb.greaterThan(balance, 100), 
        cb.lessThan(balance), 200)));

Uma consulta JPQL equivalente seria:

"select a from Account a where a.balance>100 and a.balance<200";

Predicados complexos

Certas expressões — tais como in()— se aplicam a um número variável de expressões. A listagem 7 mostra um exemplo:

Listagem 7. Expressão de valor múltiplo na CriteriaQuery
CriteriaQuery<Account> c = cb.createQuery(Account.class);
Root<Account> account = c.from(Account.class);
Path<Person> owner = account.get(Account_.owner);
Path<String> name = owner.get(Person_.name);
c.where(cb.in(name).value("X").value("Y").value("Z"));

Este exemplo navega da Account via duas etapas para criar uma expressão de caminho representando o nome do proprietário da conta. Então ele cria uma expressão in() com a expressão de caminho como entrada. A expressão in() avalia se sua expressão de entrada se iguala a qualquer um de seu número variável de argumentos. Esses argumentos são especificados através do método value() na expressão In<T>, a qual tem a seguinte assinatura de método:

In<T> value(T value);

Note como generalidades do Java são usadas para especificar que uma expressão In<T> pode ser avaliada para associação apenas com valores do tipo T. Como a expressão de caminho representando o nome do proprietário da Conta é do tipo Cadeia de caractere, a única comparação válida é entre argumentos avaliados quanto à Cadeia de caractere, o que pode ser uma expressão literal ou outra que avalia para Cadeia de caractere.

Compare a consulta na Listagem 7 com a JPQL equivalente (correta):

"select a from Account a where a.owner.name in ('X','Y','Z')";

Um leve descuido na JPQL não somente deixará de ser detectado pelo compilador, mas também produzirá um resultado indesejado. Por exemplo:

"select a from Account a where a.owner.name in (X, Y, Z)";

Unindo relacionamentos

Embora os exemplos na Listagem 6 e Listagem 7 utilizem expressões como blocos construtores, as consultas são baseadas em uma única entidade e seus atributos. Mas as consultas frequentemente envolvem mais de uma entidade, o que exige que você una duas ou mais entidades. A CriteriaQuery expressa a união de duas entidades por expressões de junção tipificadas. Uma expressão de junção tipificada tem dois parâmetros de tipo: o tipo que você está unindo e o tipo de ligação do atributo sendo unido. Por exemplo, se você deseja consultar os Customers cujo(s) PurchaseOrder(s) não foram entregues ainda, é necessário expressar isso através de uma expressão que una o Customer ao(s) PurchaseOrders, onde o Customer tenha um atributo persistente chamado orders do tipo java.util.Set<PurchaseOrder>, como demonstrado na Listagem 8:

Listagem 8. Unindo um atributo de valor múltiplo
CriteriaQuery<Customer> q = cb.createQuery(Customer.class);
Root<Customer> c = q.from(Customer.class);
SetJoin<Customer, PurchaseOrder> o = c.join(Customer_.orders);

A expressão de união criada a partir da expressão de raiz c e o atributo persistente Customer.orders é parametrizado pela fonte de união — isso é, Customer — e o tipo que pode ser ligado do atributo Customer.orders, que é PurchaseOrder e não o tipo declarado java.util.Set<PurchaseOrder>. Observe também que como o atributo original é do tipo java.util.Set, a expressão de união resultante é SetJoin, que é uma Junção para um atributo de tipo declarado java.util.Set. De forma semelhante, para outros tipos de atributo persistente de valor múltiplo suportados, a API define CollectionJoin, ListJoin e MapJoin. (A Figura 1 mostra as diversas expressões de junção.) Não é necessária uma modelagem explícita na terceira linha da Listagem 8 porque CriteriaQuery e a API metamodelo reconhece e distingue os tipos de atributos declarados como java.util.Collection ou List ou Set ou Map por métodos sobrecarregados para join().

As junções são usadas em consultas para formar um predicado na entidade unida. Portanto, se desejar selecionar os Customers com um ou mais PurchaseOrder(s) não entregues, é possível definir um predicado navegando da expressão unida o via seu atributo de status, comparando-o com o status DELIVERED e negando o predicado como:

Predicate p = cb.equal(o.get(PurchaseOrder_.status), Status.DELIVERED)
        .negate();

Um ponto que merece atenção sobre a criação de uma expressão de junção é que toda vez que você faz a junção de uma expressão, ela retorna uma nova expressão de junção, como demonstrado na Listagem 9:

Listagem 9. Cada junção cria uma instância única
SetJoin<Customer, PurchaseOrder> o1 = c.join(Customer_.orders);
SetJoin<Customer, PurchaseOrder> o2 = c.join(Customer_.orders);
assert o1 == o2;

A asserção na Listagem 9 para a igualdade de duas expressões de junção a partir da mesma expressão c com o mesmo atributo falhará. Portanto, se uma consulta envolver um predicado para PurchaseOrders que não são entregues e cujo valor é mais que $200, a construção correta deve unir PurchaseOrder com a expressão de raiz Customer apenas uma vez, atribuir a expressão de junção resultante a uma variável local (equivalente a uma faixa variável na terminologia JPQL) e utilizar a variável local na formação do predicado.

Utilizando parâmetros

Revise a consulta JPQL original neste artigo (a correta):

String jpql = "select p from Person p where p.age > 20";

Embora as consultas sejam frequentemente escritas com literais constantes, essa não é uma boa prática. A boa prática é parametrizar uma consulta, o que permite que a consulta seja analisada ou preparada somente uma vez, armazenada em cache e reutilizada. Portanto, uma forma melhor de escrever a consulta é usar um parâmetro nomeado:

String jpql = "select p from Person p where p.age > :age";

Uma consulta parametrizada liga o valor do parâmetro antes da execução da consulta:

Query query = em.createQuery(jpql).setParameter("age", 20);
List result = query.getResultList();

Em uma consulta JPQL, os parâmetros são codificados na cadeia de caractere da consulta como nomeados (precedidos por dois pontos — por exemplo, :age) ou posicional (precedido por um ponto de interrogação — por exemplo, ?3). Na CriteriaQuery, os parâmetros em si são expressões de consultas. Como qualquer outra expressão, elas são fortemente tipificadas e construídas pelo factory de expressão — a saber, QueryBuilder A consulta na Listagem 2, portanto, pode ser parametrizada como demonstrado na Listagem 10:

Listagem 10. Usando parâmetros em uma CriteriaQuery
ParameterExpression<Integer> age = qb.parameter(Integer.class);
Predicate condition = qb.gt(p.get(Person_.age), age);
c.where(condition);
TypedQuery<Person> q = em.createQuery(c); 
List<Person> result = q.setParameter(age, 20).getResultList();

Para comparar a utilização dos parâmetros com os da JPQL: a expressão de parâmetro é criada com informação de tipo explícito para ser um Integer e é usada diretamente para ligar um valor de 20 para a consulta executável. As informações de tipo extra são sempre úteis para reduzir os erros no tempo de execução, pois proíbe o parâmetro de ser comparado com uma expressão de um tipo incompatível ou ser ligado a um valor de tipo inadmissível. Nenhuma forma de segurança no tempo de compilação é garantida para os parâmetros de uma consulta JPQL.

O exemplo na Listagem 10 mostra uma expressão do parâmetro não denominada que é utilizada diretamente para ligação. Também é possível atribuir um nome ao parâmetro como segundo argumento durante sua construção. Nesse caso, é possível ligar o valor do parâmetro à consulta utilizando esse nome. O que não é possível, entretanto, é o uso de parâmetros posicionais. A posição integrar em uma cadeia de caractere (linear) de consulta JPQL faz algum sentido de forma intuitiva, mas a noção de uma posição não pode ser levada adiante no contexto da CriteriaQuery, onde o modelo conceitual é uma árvore de expressões de consulta.

Outro aspecto interessante dos parâmetros de consulta JPA é que eles não têm valor intrínseco. Um valor é ligado a um parâmetro no contexto de uma consulta executável. Portanto, é perfeitamente legal criar duas consultas executáveis separadas a partir da mesma CriteriaQuery e ligar dois valores inteiros diferentes ao mesmo parâmetro para essas consultas executáveis.

Projetando o resultado

Você viu que o tipo de resultado que uma CriteriaQuery retornará na execução é especificado à frente quando uma CriteriaQuery é construída por um QueryBuilder. O resultado da consulta é especificado como um ou mais termos de projeção. Há duas formas de especificar o termo de projeção na interface CriteriaQuery:

CriteriaQuery<T> select(Selection<? extends T> selection);
CriteriaQuery<T> multiselect(Selection<?>... selections);

O termo de projeção mais simples e frequentemente utilizado é a classe candidata da consulta em si. Pode estar implícita, como demonstrado na Listagem 11.

Listagem 11. CriteriaQuery seleciona a extensão do candidato por padrão
CriteriaQuery<Account> q = cb.createQuery(Account.class);
Root<Account> account = q.from(Account.class);
List<Account> accounts = em.createQuery(q).getResultList();

Na Listagem 11, a consulta de Account não especifica explicitamente esse termo de seleção e é o mesmo que selecionar explicitamente a classe candidata. A listagem 12 apresenta uma consulta que utiliza um termo explícito de seleção:

Listagem 12. CriteriaQuery com termo exclusivo explícito de seleção
CriteriaQuery<Account> q = cb.createQuery(Account.class);
Root<Account> account = q.from(Account.class);
q.select(account);
List<Account> accounts = em.createQuery(q).getResultList();

Quando o resultado projetado da consulta é algo diferente da entidade persistente candidata em si, várias outras construções são disponibilizadas para formar o resultado da consulta. Essas construções são disponibilizadas na interface QueryBuilder, como demonstrado na Listagem 13:

Listagem 13. Métodos para formar o resultado da consulta
<Y> CompoundSelection<Y> construct(Class<Y> result, Selection<?>... terms);
    CompoundSelection<Object[]> array(Selection<?>... terms);
    CompoundSelection<Tuple> tuple(Selection<?>... terms);

Os métodos na Listagem 13 constroem um termo de projeção composto de outras expressões selecionáveis. O método construct() cria uma instância de determinado argumento de classe e invoca um construtor com valores dos termos de seleção de entrada. Por exemplo, se CustomerDetails— uma entidade não-persistente — tiver um construtor que toma argumentos String e int, uma CriteriaQuery pode então retornar os CustomerDetails como resultado criando instâncias a partir do nome e idade do Customer selecionado de — uma entidade persistente — de instâncias, como demonstrado na Listagem 14:

Listagem 14. Formando o resultado da consulta em instâncias de uma classe por construct()
CriteriaQuery<CustomerDetails> q = cb.createQuery(CustomerDetails.class);
Root<Customer> c = q.from(Customer.class);
q.select(cb.construct(CustomerDetails.class,
              c.get(Customer_.name), c.get(Customer_.age));

Os termos múltiplos de projeção também podem ser combinados em um termo composto que representa um Object[] ou Tuple. A listagem 15 mostra como compactar o resultado em um Object[]:

Listagem 15. Formando o resultado da consulta em um Object[]
CriteriaQuery<Object[]> q = cb.createQuery(Object[].class);
Root<Customer> c = q.from(Customer.class);
q.select(cb.array(c.get(Customer_.name), c.get(Customer_.age));
List<Object[]> result = em.createQuery(q).getResultList();

Essa consulta retorna uma lista de resultados na qual cada elemento é um Object[] de comprimento 2, o elemento de array zero é o nome do Customer e o primeiro elemento é a idade do Customer.

Tuple é uma interface definida por JPA para denotar uma linha de dados. Um Tuple é conceitualmente uma lista de TupleElements — em que TupleElement é a unidade atômica e a raiz de todas as expressões de consulta. Os valores contidos em um Tuple podem ser acessados por um índice de número inteiro baseado em 0 (semelhante ao resultado JDBC familiar), um nome alternativo do TupleElement ou diretamente pelo TupleElement. A listagem 16 mostra como compactar o resultado em um Tuple:

Listagem 16. Formando o resultado da consulta em Tuple
CriteriaQuery<Tuple> q = cb.createTupleQuery();
Root<Customer> c = q.from(Customer.class);
TupleElement<String> tname = c.get(Customer_.name).alias("name");
q.select(cb.tuple(tname, c.get(Customer_.age).alias("age");
List<Tuple> result = em.createQuery(q).getResultList();
String name = result.get(0).get(name);
String age  = result.get(0).get(1);

Limitações no aninhamento

Teoricamente é possível compor formas complexas de resultados aninhando termos como um Tuple cujos elementos são Object[]s ou Tuples. Entretanto, a especificação JPA 2.0 proíbe tal aninhamento. Os termos de entrada de um multiselect() não podem ser um array ou termo composto de valor tupla. Os únicos termos compostos permitidos como argumentos multiselect() são os criados pelo método construct() (que essencialmente representa um elemento único).

Entretanto, o OpenJPA não coloca qualquer restrição no aninhamento de um termo de seleção composto dentro de outro.

Essa consulta retorna uma lista de resultados onde cada um de seus elementos é um Tuple. Cada tupla, por sua vez, carrega dois elementos — acessíveis pelo índice ou pelo alias, se houver, dos TupleElements individuais ou diretamente pelo TupleElement. Dois pontos que merecem atenção na Listagem 16 são o uso de alias(), que é uma forma de anexar um nome a qualquer expressão de consulta (criando uma nova cópia como efeito colateral) e um método createTupleQuery() no QueryBuilder, que é simplesmente uma alternativa ao createQuery(Tuple.class).

O comportamento desses métodos individuais de formação de resultado e o que é especificado como argumento de tipo de resultado da CriteriaQuery durante a construção é combinado na semântica do método multiselect(). Esse método interpreta seus termos de entrada com base no tipo de resultado da CriteriaQuery para chegar à forma do resultado. Para construir instâncias CustomerDetails como na Listagem 14 usando multiselect(), é necessário especificar a CriteriaQuery a ser do tipo CustomerDetails e simplesmente invocar multiselect() com os termos que irão compor o construtor CustomerDetails, como demonstrado na Listagem 17:

Listagem 17. multiselect() interpreta termos baseado no tipo de resultado
CriteriaQuery<CustomerDetails> q = cb.createQuery(CustomerDetails.class);
Root<Customer> c = q.from(Customer.class);
q.multiselect(c.get(Customer_.name), c.get(Customer_.age));

Como o tipo de resultado é CustomerDetails, o multiselect() interpreta seus termos de projeção de argumento como o argumento construtor para o CustomerDetails. Se a consulta for especificada para retornar um Tuple, o método multiselect() com os mesmos argumentos exatos que criariam instâncias Tuple, como mostrado na Listagem 18:

Listagem 18. Criando instâncias Tuple com multiselect()
CriteriaQuery<Tuple> q = cb.createTupleQuery();
Root<Customer> c = q.from(Customer.class);
q.multiselect(c.get(Customer_.name), c.get(Customer_.age));

O comportamento do multiselect() fica mais interessante com o Object como tipo de resultado ou se nenhum argumento de tipo for especificado. Em tais casos, se multiselect() for usado com um único termo de entrada, o valor de retorno é o termo selecionado. Mas se multiselect() contiver mais de um termo de entrada, o resultado é um Object[].


Recursos avançados

Até aqui, enfatizei principalmente a natureza fortemente tipificada da Criteria API e o fato de que ela ajuda a minimizar os erros sintáticos que causam lentidão nas consultas JPQL baseadas em cadeia de caractere. A Criteria API é também um mecanismo para construir consultas programaticamente e portanto é normalmente referida como uma API dinâmica de consulta. O poder de uma API de construção de consulta programável é limitado somente pela criatividade do seu usuário. Apresentarei quatro exemplos:

  • Utilização de uma versão fraca tipificada da API para construir consultas dinâmicas
  • Utilização de uma função suportada por banco de dados como expressão de consulta para estender a gramática
  • Edição de uma consulta para funcionalidade de busca dentro dos resultados
  • Consulta por exemplo — um padrão familiar popularizado pela comunidade de banco de dados de objeto

Tipificação fraca e construção dinâmica de consulta

A forte verificação de tipo da Criteria API é baseada na disponibilidade das classes metamodelo instanciadas no momento do desenvolvimento. Entretanto, para alguns casos de uso, as entidades a serem selecionadas podem ser determinadas no tempo de execução. Para suportar tal uso, os métodos da Criteria API oferecem uma versão paralela na qual os atributos persistentes são referenciados por seus nomes (semelhante à API de Reflexão Java) ao invés de por referência aos atributos metamodelo estáticos instanciados. Essa versão paralela da API pode suportar construção de consulta verdadeiramente dinâmica sacrificando a verificação do tipo no tempo de compilação. A Listagem 19 reescreve um exemplo na Listagem 6 utilizando a versão tipificada fraca:

Listagem 19. Consulta tipificada fraca
Class<Account> cls =Class.forName("domain.Account");
Metamodel model = em.getMetamodel();
EntityType<Account> entity = model.entity(cls); 
CriteriaQuery<Account> c = cb.createQuery(cls);
Root<Account> account = c.from(entity);
Path<Integer> balance = account.<Integer>get("balance");
c.where(cb.and
       (cb.greaterThan(balance, 100), 
        cb.lessThan(balance), 200)));

A API tipificada fraca, entretanto, não pode retornar expressões tipificadas genericamente, gerando um aviso de compilador para um modelo não verificado. Uma forma de livrar-se dessas mensagens de aviso incômodas é usar uma instalação relativamente rara de elementos genéricos Java: invocação de método parametrizado, como demonstrado na invocação da Listagem 19 do método get() para obter uma expressão de caminho.

Expressões extensíveis de armazenamento de dados

Uma vantagem distinta de um mecanismo de construção dinâmica de consulta é que a gramática é extensível. Por exemplo, é possível utilizar o método function() na interface QueryBuilder para criar uma expressão suportada pelo banco de dados:

<T> Expression<T> function(String name, Class<T> type, Expression<?>...args);

O método function() cria uma expressão do nome dado e zero ou mais expressões de entrada. A expressão de function() avalia o tipo dado. Isso permite que um aplicativo crie uma consulta que avalie uma função de banco de dados. Por exemplo, o banco de dados MySQL suporta uma função CURRENT_USER() que retorna a combinação nome de usuário e nome de host como uma sequencia codificada UTF-8 para a conta MySQL que o servidor utilizou para autenticar o cliente atual. Um aplicativo pode utilizar a função CURRENT_USER(), que não toma nenhum argumento, em uma CriteriaQuery, como demonstrado na Listagem 20:

Listagem 20. Usando parâmetros em uma CriteriaQuery
CriteriaQuery<Tuple> q = cb.createTupleQuery();
Root<Customer> c = q.from(Customer.class);
Expression<String> currentUser = 
    cb.function("CURRENT_USER", String.class, (Expression<?>[])null);
q.multiselect(currentUser, c.get(Customer_.balanceOwed));

Observe que uma consulta equivalente simplesmente não é possível para expressar em JPQL, pois tem uma gramática definida com um número fixo de expressões suportadas. Uma API dinâmica não é estritamente limitada por um conjunto fixo de expressões.

Consulta editável

Uma CriteriaQuery pode ser editado programaticamente. As cláusulas da consulta — como seus termos de seleção, predicado de seleção em uma cláusula WHERE e termos de pedido em uma cláusula ORDER BY— podem todos ser modificados. Esse recurso de edição pode ser usado em uma instalação do tipo "procura em resultado", por onde um predicado de consulta é adicionalmente refinado em etapas sucessivas adicionando mais restrições.

O exemplo na Listagem 21 cria uma consulta que solicita seu resultado por nome e então edita a consulta para classificar também pelo CEP:

Listagem 21. Editando uma CriteriaQuery
CriteriaQuery<Person> c = cb.createQuery(Person.class);
Root<Person> p = c.from(Person.class);
c.orderBy(cb.asc(p.get(Person_.name)));
List<Person> result = em.createQuery(c).getResultList();
// start editing
List<Order> orders = c.getOrderList();
List<Order> newOrders = new ArrayList<Order>(orders);
newOrders.add(cb.desc(p.get(Person_.zipcode)));
c.orderBy(newOrders);
List<Person> result2 = em.createQuery(c).getResultList();

Avaliação na memória em OpenJPA

Com os recursos estendidos do OpenJPA, o exemplo de procura nos resultados da Listagem 21 pode se tornar ainda mais eficiente avaliando a consulta editada na memória. Esse exemplo impõe que o resultado da consulta editada seja um subconjunto restrito do resultado original. Como o OpenJPA pode avaliar uma consulta na memória quando uma coleção candidata é especificada, a única modificação necessária é que a última linha da Listagem 21 forneça o resultado da consulta original:

List<Person> result2 = 
  em.createQuery(c).setCandidateCollection(result).getResultList();

Os métodos setter na CriteriaQuery select(), where() ou orderBy() — apagam os valores anteriores e os substituem com novos argumentos. A lista retornada pelos métodos getter correspondentes, tais como getOrderList() não está ativa— isto é, adicionar ou remover elementos na lista retornada não modifica o CriteriaQuery; além disso, alguns fornecedores podem até retornar uma lista imutável de proibir uso inadvertido. Portanto, uma boa prática é copiar a lista retornada em uma nova lista antes de adicionar ou remover novas expressões.

Consulta por exemplo

Outra facilidade útil em uma API de consulta dinâmica é que ela pode suportar consulta por exemplo com relativa facilidade. A consulta por exemplo (desenvolvida pela IBM® Research em 1970) é normalmente citada como um exemplo primário da usabilidade de software pelo usuário final. A ideia de consulta por exemplo é que ao invés de especificar os predicados exatos para uma consulta, uma instância de modelo é apresentada. Dada a instância de modelo, uma conjunção de predicados — onde cada um deles é uma comparação para um valor de atributo não-anulável, não-padrão da instância de modelo — é criada. A execução dessa consulta avalia o predicado para encontrar todas as instâncias que correspondem à instância de modelo. A consulta por exemplo foi considerada para inclusão na especificação JPA 2.0 não está incluída. OpenJPA suporta esse estilo de consulta através de sua interface estendida OpenJPAQueryBuilder, como demonstrado na Listagem 22 :

Listagem 22. Consulta por exemplo utilizando extensão do OpenJPA da CriteriaQuery
CriteriaQuery<Employee> q = cb.createQuery(Employee.class);

Employee example = new Employee();
example.setSalary(10000);
example.setRating(1);

q.where(cb.qbe(q.from(Employee.class), example);

Como mostra esse exemplo, a extensão do OpenJPA da interface QueryBuilder suporta a seguinte expressão:

public <T> Predicate qbe(From<?, T> from, T template);

Essa expressão produz uma conjunção de predicados baseados nos valores de atributo de determinada instância de modelo. Por exemplo, essa consulta encontrará todos os Employees com um salário de 10000 e classificação de 1. A comparação pode ser adicionalmente controlada pela especificação de uma lista opcional de atributos a serem excluídos da comparação e estilo de comparação para atributos avaliados por String. (Consulte os Recursos para um link para o Javadoc para extensões do OpenJPA CriteriaQuery.)


Conclusão

Este artigo apresentou a nova Criteria API no JPA 2.0 como mecanismo para desenvolver consultas dinâmicas, typesafe na linguagem Java. Uma CriteriaQuery é construída no tempo de execução como uma árvore de expressões de consulta fortemente tipificada que usam o artigo conforme ilustrado em uma série de exemplos de código.

Este artigo também estabelece o papel crítico da nova API metamodelo e mostra como as classes metamodelo instanciadas permitem que o compilador verifique a correção das consultas, evitando assim erros de tempo de execução causados por consultas JPQL sintaticamente incorretas. Além de reforçar a correção sintática, as facilidades JPA 2.0 para construção programática de consultas pode levar ao uso mais potente, como consulta por exemplo, utilizando funções de banco de dados e, — espero — muitos outros usos inovadores dessas poderosas novas APIs que os leitores deste artigo poderão inventar.

Agradecimentos

Agradeço a Rainer Kwesi Schweigkoffer pela cuidadosa revisão deste artigo e valiosas sugestões, e também os membros do Grupo Especializado na JPA 2.0 por explicar os pontos mais delicados desta poderosa API. Agradeço também Fay Wang por sua contribuição e Larry Kestila e Jeremy Bauer pelo apoio durante o desenvolvimento da Criteria API para OpenJPA.

Recursos

Aprender

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=439696
ArticleTitle=Consultas dinâmicas e typesafe em JPA 2.0
publish-date=05142014