Consulta de entidades JPA con JPQL y SQL nativo
Aplicación de ejemplo
Los fragmentos de código discutidos en el artículo se toman de los archivos fuente Java utilizados en la aplicación de ejemplo que acompaña al artículo. Mirando a través del archivo de muestra, puede notar que esta es una aplicación web simple basada en las tecnologías Java Servlet y Java Persistence API. Por simplicidad, no utiliza frijoles empresariales, emitiendo consultas JPQL directamente desde servlets. Sin embargo, esto no significa que no pueda utilizar las consultas JPQL discutidas aquí en enterprise beans, puede definir consultas JPQL en cualquier componente de Java EE.
La figura 1 ilustra la estructura de las entidades de ejemplo. Como puede ver, contiene un conjunto de entidades relacionadas entre sí con relaciones de diferentes tipos. Esta estructura ramificada es necesaria para ilustrar el uso de consultas de unión JPQL discutidas en la sección Definición de uniones JPQL más adelante en el artículo.
Figura 1 Relaciones entre las entidades utilizadas dentro de la aplicación de ejemplo
Para obtener instrucciones detalladas sobre cómo configurar y luego iniciar la aplicación de ejemplo, puede consultar el léame.archivo txt en el directorio raíz del archivo de ejemplo.
Uso de JPQL en aplicaciones Java EE
Si tiene alguna experiencia práctica con bases de datos, lo más probable es que ya se haya mojado con SQL, la herramienta estándar que ofrece un conjunto de instrucciones para acceder y manipular información en bases de datos relacionales. De hecho, hay muchas similitudes entre JPQL y SQL. Ambos se utilizan para acceder y manipular datos de bases de datos, a largo plazo. Y ambos usan instrucciones sin fines de lucro, comandos reconocidos por un intérprete especial. Además, JPQL es similar a SQL en su sintaxis.
La principal diferencia entre JPQL y SQL radica en que el primero trata con entidades JPA, mientras que el segundo trata directamente con datos relacionales. Como desarrollador de Java, quizás también le interese saber que el uso de JPQL, a diferencia de SQL/JDBC, elimina la necesidad de usar la API JDBC de su código Java: el contenedor hace todo este trabajo por usted entre bastidores.
JPQL le permite definir consultas mediante una de las tres instrucciones siguientes: SELECT, UPDATE o DELETE. Es interesante notar que la interfaz de la API EntityManager ofrece métodos que también se pueden usar para realizar operaciones de recuperación, actualización y eliminación sobre entidades. En particular, se trata de métodos de búsqueda, fusión y eliminación. Sin embargo, el uso de esos métodos se limita normalmente a una sola instancia de entidad, a menos que la conexión en cascada surta efecto, por supuesto. Por el contrario, las sentencias JPQL no tienen tal limitación: puede definir operaciones de actualización y eliminación masivas sobre conjuntos de entidades y definir consultas que devuelvan conjuntos de instancias de entidad.
Para emitir una consulta JPQL desde su código Java, debe utilizar los métodos apropiados de la API EntityManager y la API de consulta, realizando los siguientes pasos generales:
- 1. Obtenga una instancia de EntityManager, mediante inyección o explícitamente a través de una instancia de EntityManagerFactory.
- 2. Cree una instancia de consulta invocando el método adecuado de EntityManager, como createQuery.
- 3. Establezca un parámetro o parámetros de consulta, si los hay, utilizando el método setParameter de una consulta adecuada.
- 4. Si es necesario, establezca el número máximo de instancias a recuperar o especifique la posición de la primera instancia a recuperar, utilizando los métodos de la consulta setMaxResults o setFirstResult.
- 5. Si es necesario, establezca una sugerencia específica del proveedor, utilizando el método de consulta setHint.
- 6. Si es necesario, establezca el modo de descarga para la ejecución de la consulta con el método de consulta setFlushMode, anulando el modo de descarga del gestor de entidades.
- 7. Ejecute la consulta utilizando el método de consulta apropiado: getSingleResult o getResultList. Sin embargo, en el caso de una operación de actualización o eliminación, debe usar el método executeUpdate, que devuelve el número de instancias de entidad actualizadas o eliminadas.
La lista completa de los métodos de interfaz de EntityManager, así como de los métodos de interfaz de API de consulta, se puede encontrar en la especificación Enterprise JavaBeans 3.0: documento de API de persistencia de Java, que forma parte de JSR-220.
Ahora que tiene una idea aproximada de cómo puede crear y luego emitir una consulta JPQL, es posible que desee ver algunos ejemplos prácticos. El siguiente fragmento de código se toma del método doGet de un servlet que utiliza una consulta JPQL para obtener información sobre todos los clientes almacenados en la tabla relacional subyacente asociada con la entidad de Cliente especificada en la 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 interés son el método createQuery de la instancia EntityManager y el método getResultList de la instancia de consulta. createQuery de EntityManager se utiliza para crear la instancia de consulta cuyo método getResultList se utiliza para ejecutar la consulta JPQL pasada a createQuery como parámetro. Como puede adivinar, el método getResultList de la consulta devuelve el resultado de una consulta como una Lista cuyos elementos, en este ejemplo en particular, se convierten a type Customer.
Si necesita recuperar un solo resultado, la interfaz de la API de consultas ofrece el método getSingleResult, como se muestra en el siguiente ejemplo. Tenga en cuenta, sin embargo, que el uso de getSingleResult causará una excepción si obtiene varios resultados.
También este ejemplo ilustra el uso del método setParameter de la consulta a través del cual puede vincular un argumento a un parámetro de consulta. Con setParameter, puede enlazar parámetros nominales y posicionales. Aquí, sin embargo, enlaza un parámetro con nombre.
... 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()+""); ...
Es interesante observar que el uso de una instrucción JPQL SELECT no es el único camino a seguir cuando se trata de recuperar una sola instancia de entidad. Como alternativa, puede utilizar el método find del EntityManager, que le permite recuperar una sola instancia de entidad basada en el id de la entidad que se ha pasado como parámetro.
En algunas situaciones, es posible que necesite recuperar solo cierta información de la instancia o instancias de la entidad de destino, definiendo una consulta JPQL contra un campo o campos de entidad determinados. Así es como se vería el fragmento de código anterior, si necesita recuperar solo el valor del campo cust_name de la instancia de entidad de cliente consultada aquí:
... 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+""); ...
Del mismo modo, para obtener la lista completa de nombres de clientes, puede usar el siguiente 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/>"); } ...
Volviendo a SQL, es posible que recuerde que la lista de selección de una consulta SQL puede estar compuesta por varios campos de la tabla o tablas especificadas en la cláusula FROM. En JPQL, también puede usar una lista de selección compuesta, seleccionando los datos solo de los campos de interés de la entidad. En ese caso, sin embargo, debe crear la clase a la que enviará el resultado de la consulta. En la siguiente sección, verá un ejemplo de una consulta de unión JPQL cuya lista de selección está compuesta por los campos derivados de más de una entidad.
Definir uniones JPQL
Al igual que SQL, JPQL le permite definir consultas de unión. Sin embargo, en SQL, normalmente se define una combinación que combina registros de dos o más tablas o vistas, incluidos solo los campos obligatorios de esas tablas y vistas en la lista de selección de la consulta de combinación. Por el contrario, la lista de selección de una consulta de unión JPQL normalmente incluye una sola entidad o incluso un solo campo de entidad. La razón de esto radica en la naturaleza de la tecnología JPA. Una vez que obtenga una instancia de entidad, puede navegar a sus instancias relacionadas utilizando los métodos getter correspondientes. Este enfoque hace innecesario definir una consulta que devolverá todas las instancias de entidad relacionadas de interés a la vez.
Por ejemplo, para obtener información sobre los pedidos junto con sus elementos de línea en SQL, deberá definir una consulta de unión en las tablas purchaseOrders y orderLineItems, especificando los campos de ambas tablas en la lista de selección de la consulta. Sin embargo, al usar JPQL, puede definir una consulta solo sobre la entidad PurchaseOrder y, a continuación, navegar a las instancias OrderLineItem correspondientes utilizando el método getOrderLineItems del PurchaseOrder según sea necesario. En este ejemplo, es posible que desee definir una consulta JPQL sobre las entidades PurchaseOrder y OrderLineItem solo si necesita filtrar las instancias de PurchaseOrder recuperadas en función de una o varias condiciones aplicadas a OrderLineItem.
El siguiente fragmento muestra un ejemplo de consulta de unión JPQL en acción. Para comprender mejor cómo se relacionan entre sí las entidades involucradas, puede volver a la Figura 1 que se muestra en la sección de Aplicación de ejemplo anterior en el artículo.
... 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/>"); ...
En el ejemplo anterior, se utiliza la función de agregado MÁXIMO en la cláusula SELECT de la consulta join para determinar el producto de precio más alto, de los que han sido suministrados por Tortuga Trading y se han pedido al menos una vez.
Una situación más común, sin embargo, es cuando necesita calcular, por ejemplo, el precio total de los productos pedidos, que han sido suministrados por un proveedor determinado. Aquí es donde la función SUMA agregada puede ser útil. En SQL, una consulta de combinación de este tipo podría tener este aspecto:
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';
Desafortunadamente, la función de SUMA utilizada en JPQL no le permite pasar una expresión aritmética como argumento. Lo que esto significa en la práctica es que no podrá pasar p. price*l.quantity como argumento a la SUMA de JPQL. Sin embargo, hay maneras de solucionar este problema. En el siguiente ejemplo, se define la clase LineItemSum cuyo constructor se utiliza en la lista de selección de la consulta, tomando como parámetros p. price y l.quantity. Lo que hace el constructor LineItemSum es multiplicar p. precio por l. cantidad, guardando el resultado en su variable de clase rslt. A continuación, puede recorrer la lista LineItemSum recuperada por la consulta, sumando los valores de la variable rslt de LineItemSum. El siguiente fragmento muestra cómo se puede implementar todo esto en 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 otras cosas, el ejemplo anterior ilustra cómo se puede usar una clase Java personalizada, no una clase de entidad, en la lista de selección de la consulta JPQL que incluye los campos derivados de más de una entidad, fundiendo el resultado de la consulta en esa clase. En la mayoría de los casos, sin embargo, tendrá que lidiar con consultas que reciben una instancia o una lista de instancias de una entidad determinada.
Instancias de entidad recuperadas y el Contexto de Persistencia actual
Los resultados de la consulta en los ejemplos de artículos hasta ahora se han impreso simplemente. Sin embargo, en aplicaciones del mundo real, es posible que deba realizar algunas operaciones adicionales en los resultados de la consulta. Por ejemplo, es posible que deba actualizar las instancias recuperadas y, a continuación, volver a conservarlas en la base de datos. Esto plantea la pregunta: ¿las instancias recuperadas por una consulta JPQL están listas para ser procesadas por la aplicación, o se requieren algunos pasos adicionales para que estén listas para eso? En particular, sería interesante saber en qué estado, en relación con el contexto de persistencia actual, se encuentran las instancias de entidad recuperadas.
Si tiene alguna experiencia con la persistencia de Java, debe saber qué es un contexto de persistencia. Para recapitular, un contexto de persistencia es un conjunto de instancias de entidad administradas por la instancia EntityManager asociada a ese contexto. En los ejemplos anteriores, utilizó el método createQuery de EntityManager para crear una instancia de consulta para ejecutar una consulta JPQL. En realidad, la API EntityManager incluye más de veinte métodos para administrar el ciclo de vida de las instancias de entidad, controlar las transacciones y crear instancias de consulta cuyos métodos se utilizan para ejecutar la consulta especificada y recuperar el resultado de la consulta.
Con respecto a un contexto de persistencia, una instancia de entidad puede estar en uno de los cuatro estados siguientes: nuevo, administrado, separado o eliminado. Mediante el método de un EntityManager adecuado, puede cambiar el estado de una instancia de entidad determinada según sea necesario. Sin embargo, es interesante observar que solo las instancias en estado administrado se sincronizan con la base de datos cuando se realiza el lavado a la base de datos. Para ser precisos, las instancias de entidad en el estado eliminado también se sincronizan, lo que significa que los registros de base de datos correspondientes a esas instancias se eliminan de la base de datos.
Por el contrario, las instancias en el estado nuevo o separado no se sincronizarán con la base de datos. Por ejemplo, si crea una nueva instancia PurchaseOrder y, a continuación, invoca el método flush de EntityManager, no aparecerá otro registro en la tabla PurchaseOrder a la que se asigna la entidad PurchaseOrder. Esto se debe a que la nueva instancia PurchaseOrder no se ha adjuntado al contexto persistence. Así es como podría verse el código:
... 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 solucionar el problema, debe invocar el método persist de EntityManager para la nueva instancia PurchaseOrder antes de invocar el flush, como se muestra en el siguiente ejemplo:
... 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, siempre que haya establecido la opción de cascada en PERSIST o ALL al definir la relación con PurchaseOrder en la entidad Cliente, puede agregar la instancia de PurchaseOrder recién creada a la lista de pedidos asociados con la instancia del cliente, reemplazando la operación persist con lo siguiente:
cust.getPurchaseOrders().add(ord);
La discusión anterior sobre los estados de instancia de entidad nos lleva a la interesante pregunta de si las instancias de entidad recuperadas por una consulta JPQL se administran automáticamente, o si debe tener cuidado de establecer explícitamente su estado para que se administre. De acuerdo con la especificación JPA, independientemente de la forma en que recupere las entidades, ya sea el método find del EntityManager o una consulta, se adjuntan automáticamente al contexto de persistencia actual. Esto significa que las instancias de entidad recuperadas por una consulta JPQL se administran automáticamente. Por ejemplo, puede cambiar el valor del campo de una instancia recuperada y, a continuación, sincronizar ese cambio con la base de datos invocando el método flush del EntityManager o confirmando la transacción actual. Tampoco necesita preocuparse por el estado de las instancias asociadas con las instancias recuperadas. El hecho es que la primera vez que accede a una instancia asociada, se administra automáticamente. Aquí hay un ejemplo simple que muestra cómo funciona todo esto en la práctica:
... 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/>"); ...
Tenga en cuenta que no invoca el método persist para la instancia PurchaseOrder recuperada, ni para su instancia OrderLineItem relacionada que se modifica aquí. A pesar de este hecho, los cambios realizados en el elemento de primera línea del pedido se conservarán en la base de datos al confirmar la transacción. Esto sucede porque tanto las instancias de entidad recuperadas como sus asociaciones se adjuntan automáticamente al contexto de persistencia actual. Como se mencionó anteriormente, los primeros se administran cuando se recuperan, y los segundos se adjuntan al contexto a medida que se accede a ellos.
En algunas situaciones, es posible que desee que las asociaciones se adjunten al contexto en la ejecución de la consulta, en lugar del primer acceso. Aquí es donde una UNIÓN DE BÚSQUEDA es útil. Digamos que desea obtener todos los pedidos que pertenecen a un determinado cliente, al recuperar esa instancia de cliente. Este enfoque garantiza que está tratando con los pedidos de los clientes disponibles en el momento de la ejecución de la consulta. Si, por ejemplo, se agrega un nuevo pedido a otro contexto y, a continuación, se sincroniza con la base de datos antes de acceder por primera vez a la lista de pedidos asociados con la instancia de cliente recuperada, no verá este cambio hasta que actualice el estado de la instancia de cliente de la base de datos. En el siguiente fragmento, se utiliza la consulta de unión que devuelve la instancia de cliente cuyo id de cliente es 1 y obtiene las instancias de orden de compra asociadas con la instancia de cliente que se recupera.
... 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(); ...
Al no formar parte del resultado explícito de la consulta, las instancias de entidad PurchaseOrder asociadas con la instancia de cliente recuperada aquí también se recuperan y se adjuntan al contexto de persistencia actual al ejecutar la consulta.
Utilizando consultas SQL nativas
Es interesante observar que no está limitado a JPQL al definir consultas que luego se ejecutarán con la API de consultas. Puede que le sorprenda saber que la API EntityManager ofrece métodos para crear instancias de consulta para ejecutar sentencias SQL nativas. Lo más importante que hay que entender sobre las consultas SQL nativas creadas con métodos EntityManager es que, como las consultas JPQL, devuelven instancias de entidad en lugar de registros de tablas de base de datos. Este es un ejemplo sencillo de una 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 todavía está evolucionando, y no tiene muchas de esas características importantes disponibles en SQL. En la sección Defining JPQL Joins anterior, vio un ejemplo de lo incompleto de JPQL: tuvo que hacer mucho trabajo por su cuenta porque la función SUM aggregate de JPQL no puede tomar una expresión aritmética como parámetro. Por el contrario, la función SUM de SQL no tiene tal limitación. Por lo tanto, este es un buen ejemplo de donde reemplazar JPQL con SQL nativo podría ser eficiente. El siguiente código ilustra cómo puede simplificar las cosas en este ejemplo en particular al elegir SQL nativo en lugar 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 otras cosas, el ejemplo anterior ilustra que puede enlazar argumentos a parámetros de consulta nativos. En particular, puede enlazar argumentos a parámetros posicionales de la misma manera que si estuviera tratando con una consulta JPQL.
La principal desventaja de las consultas nativas es la complejidad del enlace de resultados. En el ejemplo, la consulta produce un único resultado de un tipo simple, evitando así este problema. En la práctica, sin embargo, a menudo tiene que lidiar con un conjunto de resultados de tipo complejo. En este caso, tendrá que declarar una entidad a la que puede asignar su consulta nativa, o definir un conjunto de resultados complejo asignado a varias entidades o a una combinación de entidades y resultados escalares.
Usar procedimientos almacenados
Otra desventaja de las consultas nativas es que el código Java se vuelve directamente dependiente de la estructura de base de datos subyacente. Si modifica esa estructura subyacente, tendrá que ajustar las consultas nativas correspondientes en sus servlets y / u otros componentes de la aplicación, teniendo que recompilar y redistribuir esos componentes después de eso. Para solucionar este problema, sin dejar de usar consultas nativas, puede aprovechar los procedimientos almacenados, mover consultas SQL complejas a programas almacenados y ejecutados dentro de la base de datos y, a continuación, llamar a esos programas almacenados en lugar de realizar llamadas directas a las tablas subyacentes. Lo que esto significa en la práctica es que los procedimientos almacenados pueden ahorrarle la molestia de tratar con las tablas subyacentes directamente de las consultas que están codificadas en su código Java. La ventaja de este enfoque es que, en la mayoría de los casos, no necesitará modificar su código Java para seguir los cambios en la estructura de la base de datos subyacente. En su lugar, solo los procedimientos almacenados necesitarán reparación.
Volviendo al ejemplo descrito en la sección anterior, puede mover la consulta de combinación compleja utilizada allí a una función almacenada, creada de la siguiente manera:
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; /
Esto simplifica la consulta nativa utilizada en el código Java y elimina la dependencia de las tablas subyacentes:
... 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/>"); ...
Conclusión
Como ha aprendido en este artículo, JPQL es una herramienta poderosa cuando se trata de acceder a datos relacionales desde aplicaciones Java que utilizan persistencia Java. Con JPQL, a diferencia de SQL/JDBC, se definen consultas sobre entidades JPA asignadas a tablas de base de datos subyacentes en lugar de consultar esas tablas directamente, con lo que se trata de una capa de abstracción que oculta los detalles de la base de datos de la capa de lógica de negocio. También aprendió que JPQL no es la única opción cuando se trata de crear consultas sobre entidades JPA, en algunas situaciones usar consultas SQL nativas es más conveniente.