Questionar as entidades JPA com JPQL e SQL nativo

aplicação de amostras

os excertos de código discutidos no artigo são retirados dos ficheiros fonte Java usados na aplicação de amostras que acompanha o artigo. Olhando através do arquivo sample, você pode notar que esta é uma aplicação web simples baseada nas tecnologias Java Servlet e Java Persistence API. Para simplificar, não utiliza feijão de empresa, emitindo consultas JPQL diretamente de dentro de servlets. Isso não significa, no entanto, que você não será capaz de utilizar as consultas JPQL discutidas aqui na enterprise beans—você pode definir consultas JPQL em quaisquer componentes Java EE.

a Figura 1 ilustra a estrutura das entidades amostrais. Como você pode ver, ele contém um conjunto de entidades relacionadas entre si com relacionamentos de diferentes tipos. Tal estrutura ramificada é necessária para ilustrar a utilização de consultas de juntas JPQL discutidas na secção de juntas JPQL que define mais tarde no artigo.

Figura 1 relações entre as entidades utilizadas dentro da aplicação de amostra

para uma instrução detalhada sobre como configurar e, em seguida, lançar o aplicativo de amostra, você pode se referir ao readme.o ficheiro txt na pasta de topo do pacote de amostras.

usando JPQL em aplicações Java EE

se você tem alguma experiência prática com bases de dados, você provavelmente já tem seus pés molhados com SQL, a ferramenta padrão que oferece um conjunto de declarações para acessar e manipular informações em bases de dados relacionais. Na verdade, há muitas semelhanças entre JPQL e SQL. Ambos são usados para acessar e manipular dados de banco de dados, a longo prazo. E ambos usam declarações não processurais-comandos reconhecidos por um intérprete especial. Além disso, JPQL é similar ao SQL em sua sintaxe.

a principal diferença entre a JPQL e a SQL reside no facto de a primeira tratar de entidades da JPA, enquanto a segunda trata directamente de dados relacionais. Como um desenvolvedor Java, você também talvez esteja interessado em aprender que usando JPQL, ao contrário do SQL/JDBC, elimina a necessidade de você usar a API JDBC do seu código Java—O container faz tudo isso para você nos bastidores.

o JPQL permite-lhe definir consultas com uma das seguintes três instruções: seleccione, actualize ou remova. É interessante notar que a interface API EntityManager oferece métodos que também podem ser usados para realizar operações de recuperação, atualização e exclusão sobre entidades. Em particular, estes são encontrar, mesclar e remover métodos. O uso desses métodos, no entanto, é tipicamente limitado a uma única instância de entidade, a menos que cascading faça efeito, é claro. Em contraste, as declarações JPQL não têm tal limitação—você pode definir operações de atualização e remoção de massa sobre conjuntos de Entidades, e definir consultas retornando conjuntos de instâncias de entidade.

para emitir uma consulta JPQL dentro de seu código Java, você tem que utilizar métodos apropriados da API EntityManager e da API da consulta, executando os seguintes passos gerais:

  • 1. Obter uma instância de EntityManager, usando injeção ou explicitamente através de uma instância EntityManagerFactory.
  • 2. Crie uma instância de consulta, invocando um método apropriado do EntityManager, como createQuery.
  • 3. Defina um parâmetro ou parâmetros da consulta, Se houver, usando o método setParameter de uma consulta apropriada.
  • 4. Se necessário, defina o número máximo de instâncias para recuperar e/ou especificar a posição da primeira instância para recuperar, usando os métodos da consulta setmaxresultados e/ou setFirstResult.
  • 5. Se necessário, defina uma dica específica do Fornecedor, usando o método da consulta setHint.
  • 6. Se necessário, defina o modo de descarga para a execução da consulta com o método da consulta setFlushMode, sobrepondo o modo de descarga do gestor da entidade.
  • 7. Execute a consulta usando o método de uma consulta apropriada: getSingleResult ou getResultList. No caso de uma operação de atualização ou delete, no entanto, você deve usar o método executeUpdate, que retorna o número de instâncias da entidade atualizadas ou apagadas.

the full list of the EntityManager interface methods, as well as the Query API interface methods, can be found in the Enterprise JavaBeans 3.0 Specification: Java Persistence API document, which is part of JSR-220.

Agora que você tem uma idéia aproximada de como você pode criar e, em seguida, emitir uma consulta JPQL, você pode querer ver alguns exemplos práticos. O seguinte fragmento de código é retirado do método doGet de um servlet que usa uma consulta JPQL para obter informações sobre todos os clientes armazenados na tabela relacional subjacente associada com a entidade cliente especificada na consulta.

  ... @PersistenceUnit private EntityManagerFactory emf; public void doGet( ... EntityManager em = emf.createEntityManager(); PrintWriter out = response.getWriter(); List<Customer> arr_cust = (List<Customer>)em.createQuery("SELECT c FROM Customer c") .getResultList(); out.println("List of all customers: "+""); Iterator i = arr_cust.iterator(); Customer cust; while (i.hasNext()) { cust = (Customer) i.next(); out.println(cust.getCust_id()+""); out.println(cust.getCust_name()+""); out.println(cust.getEmail()+""); out.println(cust.getPhone()+""); out.println("----------------" + ""); } ... 

de especial interesse aqui estão o método createQuery da instância EntityManager e o método getResultList da instância de consulta. O Criatequery do EntityManager é usado para criar a instância de consulta cujo método getResultList é então usado para executar a consulta JPQL passada para createQuery como o parâmetro. Como você pode adivinhar, o método getResultList da consulta retorna o resultado de uma consulta como uma lista cujos elementos, neste exemplo particular, são lançados para digitar o cliente.

se você precisa recuperar um único resultado, a interface API da consulta oferece o método getSingleResult, como mostrado no exemplo seguinte. Note, no entanto, que o uso de getSingleResult causará uma exceção se você receber vários resultados de volta.

também este exemplo ilustra o uso do método setParameter da consulta através do qual você pode ligar um argumento a um parâmetro da consulta. Com setParameter, você pode ligar ambos os parâmetros nomeados e posicionais. Aqui, no entanto, você liga um parâmetro nomeado.

  ... Integer cust_id =2; Customer cust = (Customer)em.createQuery("SELECT c FROM Customer c WHERE c.cust_id=:cust_id") .setParameter("cust_id", cust_id) .getSingleResult(); out.println("Customer with id "+cust.getCust_id()+" is: "+ cust.getCust_name()+""); ... 

é interessante notar que o uso de uma ESCOLHA JPQL declaração não é a única maneira de ir quando se trata de recuperar uma única instância de entidade. Alternativamente, você pode utilizar o método EntityManager find, que permite que você recupere uma única instância de entidade com base no id da entidade passado como o parâmetro.

em algumas situações, você pode precisar recuperar apenas algumas informações da instância ou instâncias da entidade alvo, definindo uma consulta JPQL contra um determinado campo ou campos da entidade. Isto é o que o trecho acima seria semelhante, se você precisar recuperar apenas o valor do cust_name campo do Cliente instância da entidade consultada aqui:

  ... Integer cust_id =2; String cust_name = (String)em.createQuery("SELECT c.cust_name FROM Customer c WHERE c.cust_id=:cust_id") .setParameter("cust_id", cust_id) .getSingleResult(); out.println("Customer with id "+cust_id+" is: "+cust_name+""); ... 

da mesma forma, para obter toda a lista de nomes de clientes, você pode usar o seguinte código:

  ... List<String> arr_cust_name = (List<String>)em.createQuery("SELECT c.cust_name FROM Customer c") .getResultList(); out.println("List of all customers: "+"<br/>"); Iterator i = arr_cust_name.iterator(); String cust_name; while (i.hasNext()) { cust_name = (String) i.next(); out.println(cust_name+"<br/>"); } ... 

voltando ao SQL, você pode lembrar que a lista de seleção de uma consulta SQL pode ser composta por vários campos da tabela ou tabelas especificadas na cláusula de FROM. Em JPQL, você também pode usar uma lista de seleção composta, selecionando os dados apenas dos campos de interesse da entidade. Nesse caso, no entanto, você precisa criar a classe para a qual você vai lançar o resultado da consulta. Na seção seguinte, você verá um exemplo de uma consulta JPQL join cuja lista de seleção é composta dos campos derivados de mais de uma entidade.

definir as ligações JPQL

tal como o SQL, o JPQL permite-lhe definir as consultas de junção. No SQL, no entanto, você normalmente define uma junção que combina registros de duas ou mais tabelas e/ou vistas, incluindo apenas os campos necessários a partir dessas tabelas e vistas na lista de seleção da consulta de adesão. Em contraste, a lista selecionada de uma consulta JPQL join normalmente inclui uma única entidade ou mesmo um único campo de entidade. A razão para isso reside na natureza da tecnologia App. Uma vez que você obtém uma instância de entidade, você pode então navegar para as instâncias relacionadas usando os métodos getter correspondentes. Esta abordagem torna desnecessário para você definir uma consulta que irá retornar todas as instâncias de entidade relacionadas de interesse de uma vez.

por exemplo, para obter informações sobre as encomendas, juntamente com os seus itens de linha em SQL, é necessário definir uma consulta de adesão tanto nas tabelas de compra como nas tabelas de orderLineItems, especificando os campos de ambos os quadros na lista de selecção da consulta. Ao usar JPQL, no entanto, você pode definir uma consulta apenas sobre a Entidade de compra, e então navegar para as instâncias correspondentes OrderLineItem usando o método de compra getOrderLineItems conforme necessário. Neste exemplo, você pode querer definir uma consulta JPQL sobre o PurchaseOrder e OrderLineItem entidades apenas se você precisar filtrar página visitada em PurchaseOrder instâncias com base em uma condição, ou condições aplicadas para OrderLineItem.

o seguinte excerto mostra um exemplo de consulta JPQL join em ação. Para entender melhor como as entidades envolvidas estão relacionadas umas com as outras, Você pode voltar para a Figura 1 mostrada na seção de Aplicação de amostras anteriormente no artigo.

  ... Double max = (Double) em.createQuery("SELECT MAX(p.price) FROM PurchaseOrder o JOIN o.orderLineItems l JOIN l.product p JOIN p.supplier s WHERE s.sup_name = 'Tortuga Trading'") .getSingleResult(); out.println("The highest price for an ordered product supplied by Tortuga Trading: "+ max + "<br/>"); ... 

no exemplo acima, você usa a função agregada MAX na cláusula de seleção da consulta join, a fim de determinar o produto de preço mais alto, daqueles que foram fornecidos pela Tortuga Trading e foram encomendados pelo menos uma vez.

uma situação mais comum, no entanto, é quando você precisa calcular, por exemplo, o preço total dos produtos encomendados, que foram fornecidos por um determinado fornecedor. É aqui que a função agregada soma pode vir a ser útil. Em SQL, tal consulta de adesão pode parecer-se com este:

  SELECT SUM(p.price*l.quantity) FROM purchaseorders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name ='Tortuga Trading'; 

infelizmente, a função SUM usada em JPQL não permite que você passe uma expressão aritmética como o argumento. O que isso significa na prática é que você não será capaz de passar p.price*l. quantidade como o argumento para a soma do JPQL. No entanto, há formas de resolver esta questão. No exemplo seguinte, você define Classe LineItemSum cujo construtor é então usado na lista selecionada da consulta, tomando p.price e L. quantity como os parâmetros. O que o construtor LineItemSum faz é multiplicar o preço p. por quantidade l., economizando o resultado para a sua variável classe rslt. Em seguida, você pode iterar através da lista de LineItemSum obtida pela consulta, somando os valores da variável Rslt do LineItemSum. O seguinte excerto mostra como tudo isso pode ser implementado em código:

  package jpqlexample.servlets; ... class LineItemSum { private Double price; private Integer quantity; private Double rslt; public LineItemSum (Double price, Integer quantity){ this.rslt = quantity*price; } public Double getRslt () { return this.rslt; } public void setRslt (Double rslt) { this.rslt = rslt; } } public class JpqlJoinsServlet extends HttpServlet { ... public void doGet( ... List<LineItemSum> arr = (List<LineItemSum>)em.createQuery ("SELECT NEW jpqlexample.servlets.LineItemSum(p.price, l.quantity) FROM PurchaseOrder o JOIN o.orderLineItems l JOIN l.product p JOIN p.supplier s WHERE s.sup_name = 'Tortuga Trading'") .getResultList(); Iterator i = arr.iterator(); LineItemSum lineItemSum; Double sum = 0.0; while (i.hasNext()) { lineItemSum = (LineItemSum) i.next(); sum = sum + lineItemSum.getRslt(); } out.println("The total cost of the ordered products supplied by Tortuga Trading: "+ sum + "<br/>"); } } 

Entre outras coisas, o exemplo acima ilustra como você pode usar uma classe Java personalizada, não é uma entidade de classe, na consulta JPQL lista select que inclui os campos derivada de mais de uma entidade, lançando o resultado da consulta para essa classe. Na maioria dos casos, no entanto, você terá que lidar com consultas que recebem uma instância ou uma lista de instâncias de uma determinada entidade.

retrieved Entity Instances and the Current Persistence Context

The query results in the article examples so far have been simply printed out. Em aplicações do mundo real, no entanto, você pode precisar realizar algumas operações adicionais sobre os resultados da consulta. Por exemplo, você pode precisar atualizar as instâncias recuperadas e, em seguida, persisti-las de volta para o banco de dados. Isto levanta a questão: as instâncias que estão sendo recuperadas por uma consulta JPQL estão prontas para serem processadas pela aplicação, ou alguns passos adicionais são necessários para torná-las prontas para isso? Em particular, seria interessante aprender em que estado, no que diz respeito ao atual contexto de persistência, instâncias de entidade recuperadas são.

se você tem alguma experiência com persistência Java, você deve saber o que é um contexto de persistência. Para recapitular, um contexto de persistência é um conjunto de instâncias de entidade gerenciadas pela instância EntityManager associada a esse contexto. Nos exemplos anteriores, você usou o método de createQuery do EntityManager para criar uma instância de consulta para executar uma consulta JPQL. Na verdade, a API EntityManager inclui mais de vinte métodos para gerenciar o ciclo de vida de instâncias da entidade, controlar transações, e criar instâncias de consulta cujos métodos são então usados para executar a consulta especificada e recuperar o resultado da consulta.

em relação a um contexto de persistência, uma instância de entidade pode estar em um dos quatro estados seguintes: novo, gerenciado, destacado ou removido. Usando um método apropriado do EntityManager, você pode alterar o estado de uma determinada instância de entidade conforme necessário. É interessante notar, no entanto, que apenas as instâncias em estado gerenciado são sincronizadas para o banco de dados, quando a descarga para o banco de dados ocorre. Para ser preciso, as instâncias da entidade no estado removido também são sincronizadas, o que significa que os registros de banco de dados correspondentes a essas instâncias são removidos do banco de dados.

em contraste, instâncias no Estado Novo ou destacado não serão sincronizadas com a base de dados. Por exemplo, se criar uma nova instância de compra e, em seguida, invocar o método de descarga do Gestor de direitos, não aparecerá outro registo na tabela de compra para a qual a entidade de compra está mapeada. Tal deve-se ao facto de a nova instância de compra não ter sido associada ao contexto de persistência. Aqui está o que o código pode parecer:

  ... em.getTransaction().begin(); Customer cust = (Customer) em.find(Customer.class, 1); PurchaseOrder ord = new PurchaseOrder(); ord.setOrder_date(new Date()); ord.setCustomer(cust); em.getTransaction().commit(); ... 

Para corrigir o problema, você precisa invocar o EntityManager do persistem método para o novo PurchaseOrder instância antes de invocar o flush, como mostrado no exemplo a seguir:

  ... em.getTransaction().begin(); Customer cust = (Customer) em.find(Customer.class, 1); PurchaseOrder ord = new PurchaseOrder(); ord.setOrder_date(new Date()); ord.setCustomer(cust); em.persist(ord); em.getTransaction().commit(); ... 

alternativamente, desde que você tenha definido a opção cascade para persistir ou tudo ao definir a relação com a compra order na entidade do cliente, você pode adicionar a instância de compra recém-criada para a lista das ordens associadas com a instância do cliente, substituindo a operação persistir com o seguinte:

  cust.getPurchaseOrders().add(ord); 

a discussão acima sobre os estados de instância de entidade nos leva à interessante questão de se as instâncias de entidade recuperadas por uma consulta JPQL se tornam gerenciadas automaticamente, ou você tem que ter o cuidado de definir explicitamente seu estado a ser gerenciado. De acordo com a especificação da JPA, independentemente da forma como você recupera as entidades—seja o método de pesquisa do EntityManager ou uma consulta—elas são automaticamente anexadas ao contexto de persistência atual. Isto significa que as instâncias de entidade recuperadas por uma consulta JPQL se tornam gerenciadas automaticamente. Você pode, por exemplo, alterar o valor do campo de uma instância recuperada e, em seguida, sincronizar essa alteração para o banco de dados, invocando o método de descarga do EntityManager ou cometendo a transação atual. Você também não precisa se preocupar com o estado das instâncias associadas com as instâncias recuperadas. O fato é que a primeira vez que você acessar uma instância associada ela se torna gerenciada automaticamente. Aqui está um exemplo simples mostrando como tudo isso funciona na prática:

  ... em.getTransaction().begin(); PurchaseOrder ord = (PurchaseOrder)em.createQuery("SELECT o FROM PurchaseOrder o WHERE o.pono = 1") .getSingleResult(); List<OrderLineItem> items = ord.getOrderLineItems(); Integer qnt = items.get(0).getQuantity(); out.println("Quantity of the first item : "+ qnt +"<br/>"); items.get(0).setQuantity(qnt+1); qnt = items.get(0).getQuantity(); em.getTransaction().commit(); out.println("Quantity of the first item : "+ qnt +"<br/>"); ... 

Note que você não invoca o método persistent para a instância retrieved PurchaseOrder, nem para a sua instância OrderLineItem relacionada sendo modificada aqui. Apesar deste fato, as alterações feitas ao item da primeira linha na ordem serão persistidas na base de dados ao cometer a transação. Isso acontece porque tanto as instâncias da entidade recuperada e suas associações são automaticamente anexadas ao contexto de persistência atual. Como mencionado anteriormente, os primeiros tornam-se geridos quando são recuperados, e os últimos são anexados ao contexto como você acessá-los.

em algumas situações, você pode querer que associações sejam anexadas ao contexto na execução da consulta, em vez de no primeiro acesso. É aqui que uma busca vem a calhar. Diga, você quer obter todas as ordens pertencentes a um determinado cliente, ao recuperar a instância do cliente. Esta abordagem garante que você está lidando com as ordens de clientes disponíveis no momento da execução da consulta. Se, por exemplo, uma nova ordem é adicionada a outro contexto e, em seguida, sincronizada para o banco de dados antes de acessar pela primeira vez a lista de ordens associadas com a instância do cliente recuperada, você não verá esta alteração até que você atualizar o estado da instância do cliente a partir do banco de dados. No seguinte excerto, você usa a consulta join que retorna a instância do cliente cujo cust_id é 1, e obtém as instâncias de compra associadas com a instância do cliente que está sendo recuperada.

  ... Customer cust = (Customer)em.createQuery("SELECT DISTINCT c FROM Customer c LEFT JOIN FETCH c.purchaseOrders WHERE c.cust_id=1") .getSingleResult(); ... List<PurchaseOrder> orders = cust.getPurchaseOrders(); ... 

não fazendo parte do resultado explícito da consulta, as instâncias de entidade de compra associadas com a instância de cliente recuperada aqui também são recuperadas e anexadas ao contexto de persistência atual após a execução da consulta.

Utilizing Native SQL Queries

é interessante notar que você não está limitado a JPQL ao definir consultas a serem então executadas com consulta API. Você pode se surpreender ao saber que a API EntityManager oferece métodos para criar instâncias de consulta para executar declarações SQL nativas. A coisa mais importante para entender sobre consultas SQL nativas criadas com métodos EntityManager é que eles, como consultas JPQL, retornam instâncias de entidade, em vez de registros de tabela de banco de dados. Aqui está um exemplo simples de uma consulta SQL nativa dinâmica:

  ... List<Customer> customers = (List<Customer>)em.createNativeQuery ("SELECT * FROM customers", jpqlexample.entities.Customer.class) .getResultList(); Iterator i = customers.iterator(); Customer cust; out.println("Customers: " + "<br/>"); while (i.hasNext()) { cust = (Customer) i.next(); out.println(cust.getCust_name() +"<br/>"); } ... 

JPQL ainda está evoluindo, e não tem muitas dessas características importantes disponíveis no SQL. Na secção Definir JPQL junta-se à secção anterior, você viu um exemplo da incompletude do JPQL: você teve que fazer muito trabalho por conta própria porque a função SOMA agregada do JPQL não pode ter uma expressão aritmética como parâmetro. Em contraste, a função de soma de SQL não tem tal limitação. Então, este é um bom exemplo de onde substituir JPQL por SQL nativo poderia ser eficiente. O código a seguir ilustra como você pode simplificar as coisas neste exemplo em particular, escolhendo nativo SQL através de JPQL:

  ... String sup_name ="Tortuga Trading"; BigDecimal sum = (List)em.createNativeQuery("SELECT SUM(p.price*l.quantity) FROM orders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name =?1") .setParameter(1, sup_name) .getSingleResult(); out.println("The total cost of the ordered products supplied by Tortuga Trading: " + sum +"<br/>"); ... 

Entre outras coisas, o exemplo acima ilustra que você pode vincular argumentos nativo parâmetros de consulta. Em particular, você pode vincular argumentos a parâmetros posicionais da mesma forma como se estivesse lidando com uma consulta JPQL.

a principal desvantagem das consultas nativas é a complexidade da ligação do resultado. No exemplo, a consulta produz um único resultado de um tipo simples, evitando assim este problema. Na prática, no entanto, muitas vezes você tem que lidar com um conjunto de resultados de um tipo complexo. Neste caso, você terá que declarar uma entidade para a qual você pode mapear sua consulta nativa, ou definir um conjunto de resultados complexos mapeados para múltiplas entidades ou para uma mistura de Entidades e resultados escalares.

usando procedimentos armazenados

outra desvantagem das consultas nativas é que o seu código Java torna-se diretamente dependente da estrutura de base de dados subjacente. Caso você modifique essa estrutura subjacente, você terá que ajustar as consultas nativas em causa em seus servlets e/ou outros componentes de aplicação, tendo que recompilar e redistribuir esses componentes Depois disso. Para contornar este problema, ainda usando as consultas nativas, você pode tirar vantagem de procedimentos armazenados, movimento complexo de consultas SQL em programas armazenados e executados dentro do banco de dados e, em seguida, chamar os programas armazenados em vez de fazer chamadas diretas para as tabelas subjacentes. O que isso significa na prática é que os procedimentos armazenados podem lhe poupar o trabalho de lidar com as tabelas subjacentes diretamente das consultas que são codificadas em seu código Java. O benefício desta abordagem é que na maioria dos casos você não precisará modificar seu código Java para acompanhar as alterações na estrutura de banco de dados subjacente. Em vez disso, só os procedimentos armazenados terão de ser corrigidos.

Voltando ao exemplo discutido na seção anterior, você pode mover a associação complexa de consulta utilizada para uma função armazenada, criado da seguinte maneira:

  CREATE OR REPLACE FUNCTION sum_total(supplier VARCHAR2) RETURN NUMBER AS sup_sum NUMBER; BEGIN SELECT SUM(p.price*l.quantity) INTO sup_sum FROM orders o JOIN orderlineitems l ON o.pono=l.pono JOIN products p ON l.prod_id=p.prod_id JOIN suppliers s ON p.sup_id=s.sup_id WHERE sup_name = supplier; RETURN sup_sum; END; / 

Isso simplifica a consulta nativa utilizados em seu código Java e remove a dependência das tabelas subjacentes:

  ... String sup_name ="Tortuga Trading"; BigDecimal sum = (BigDecimal)em.createNativeQuery("SELECT sum_total(?1) FROM DUAL") .setParameter(1, sup_name) .getSingleResult(); out.println("The total cost of the ordered products supplied by Tortuga Trading: " + sum +"<br/>"); ... 

conclusão

como você aprendeu neste artigo, JPQL é uma ferramenta poderosa quando se trata de acessar dados relacionais de dentro de aplicações Java utilizando persistência Java. Com o JPQL, ao contrário do SQL / JDBC, você define consultas sobre entidades da JPA mapeadas para tabelas de banco de dados subjacentes, em vez de questionar essas tabelas diretamente, lidando assim com uma camada de abstração que esconde detalhes de banco de dados da camada lógica de negócios. Você também aprendeu que JPQL não é a única opção quando se trata de criar consultas sobre entidades da JPA—em algumas situações usando consultas nativas SQL é mais conveniente.