Java持久化查询语言(JPQL)的使用指南
在开发使用Java持久化API(JPA)的应用程序时,我们经常需要从数据库中检索和操作实体实例。传统的通过主键检索实体的方法存在一定局限性,而Java持久化查询语言(JPQL)则提供了更强大的解决方案。
1. JPQL概述
JPQL在很多方面与SQL相似,它们都是用于与关系型数据库交互的工具,通过非过程化语句来访问和操作数据。但两者也存在关键区别:
-
操作对象
:SQL直接操作数据库对象,如特定的表或视图;而JPQL处理的是实体,查询结果也是实体实例,而非表或视图记录。
-
数据库独立性
:JPQL是一种与数据库供应商无关的语言,无论实体映射到何种底层数据库,对JPQL来说都没有区别。
使用JPQL的好处还体现在简化实体实例的检索任务上。在事务上下文中调用查询方法时,返回的所有实体实例都会自动被管理,无需执行合并操作。
2. 何时使用JPQL
在开发使用JPA的应用程序时,选择JPQL而非SQL有以下几个原因:
-
检索实体集合
:当需要根据非主键列的条件检索一组实体实例时,JPQL非常合适。它允许在SELECT语句的WHERE子句中指定条件来限制查询结果,同时也支持GROUP BY、HAVING和ORDER BY子句。
-
执行连接查询
:可以对具有关系的实体执行连接查询。
-
批量操作
:不仅可以执行SELECT查询,还能对特定实体类或继承层次结构的实体执行批量删除和更新操作。
-
可移植性
:由于JPQL是数据库供应商无关的语言,使用它创建的查询可以在不同的底层数据库上移植。
3. JPQL支持的操作
JPQL支持的语句有三种:
-
SELECT
-
UPDATE
-
DELETE
其中,SELECT语句除了必需的SELECT和FROM子句外,还有一些可选子句,如下表所示:
| 子句 | 描述 |
| — | — |
| WHERE | 该子句中指定的条件用于限制查询的结果集。 |
| GROUP BY | 根据该子句中指定的实体字段的值,将检索到的实体实例集合划分为多个组。 |
| HAVING | 与GROUP BY结合使用,将检索到的实体组限制为满足指定条件的组。 |
| ORDER BY | 根据该子句中指定的实体字段的排序规则,对检索到的实体实例集合进行排序。 |
此外,JPQL还提供了一组可在查询子句中使用的运算符和函数,其语法和用法大多与SQL相似。
4. 处理JPQL语句
EntityManager接口提供了两个方法,可与Query接口方法结合使用,来创建、绑定和执行JPQL语句,如下表所示:
| 方法 | 描述 |
| — | — |
| public Query createQuery(String jpql_stmt) | 创建一个在业务逻辑代码中定义的动态查询。它接受一个JPQL语句字符串作为参数,并返回一个用于执行该JPQL语句的Query实例。 |
| public Query createNamedQuery(String query_name) | 创建一个在元数据中定义的静态查询。它接受一个使用NamedQuery注解定义的JPQL查询或使用NamedNativeQuery注解定义的原生SQL查询的名称,并返回一个用于执行该命名查询的Query实例。 |
以下是一个创建和执行SELECT查询的示例:
Query query = em.createQuery("SELECT e FROM Employee e");
List<Employee> employees = (List<Employee>)query.getResultList();
在实际应用中,也可以使用以下语法:
List<Employee> employees = (List<Employee>)em.createQuery(
"SELECT e FROM Employee e")
.getResultList();
5. 使用Query API
创建Query实例后,需要执行查询并检索结果,这些任务以及参数绑定和分页控制都由Query接口方法处理。以下是一些常用的Query接口方法:
| 方法 | 描述 |
| — | — |
| public List getResultList() | 执行由EntityManager的createQuery或createNamedQuery方法创建的SELECT查询,并将一组实体作为列表检索。 |
| public Object getSingleResult() | 执行由EntityManager的createQuery或createNamedQuery方法创建的SELECT查询,并将单个实体作为结果检索。 |
| public int executeUpdate() | 执行由EntityManager的createQuery或createNamedQuery方法创建的UPDATE或DELETE查询,并返回受影响的实体数量。 |
| public Query setMaxResults(int maxResult) | 定义要检索的实体实例的最大数量,并返回相同的Query实例。 |
| public Query setFirstResult(int startPosition) | 定义要检索的第一个实体实例的位置,并返回相同的Query实例。 |
| public Query setHint(String hintName, Object value) | 指定特定于供应商的提示,并返回相同的Query实例。 |
| public Query setParameter(String name, Object value) | 执行命名参数绑定,并返回相同的Query实例。 |
| public Query setParameter(String name, Date value, TemporalType temporalType) | 将Date实例绑定到命名参数,并返回相同的Query实例。 |
| public Query setParameter(int position, Object value) | 执行位置参数绑定,并返回相同的Query实例。 |
| public Query setParameter(int position, Date value, TemporalType temporalType) | 将Date实例绑定到位置参数,并返回相同的Query实例。 |
6. 使用JPQL检索实体
6.1 简单示例
以下是一个简单的示例,展示了如何使用JPQL检索所有员工实体实例:
// JpqlTestBean.java
package ejbjpa.ejb;
import java.io.Serializable;
import javax.ejb.EJBException;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import ejbjpa.entities.*;
@Stateless
public class JpqlTestBean implements JpqlTest {
@PersistenceContext
private EntityManager em;
public List<Employee> getEmployees() {
List<Employee> employees = null;
try {
employees = (List<Employee>)em.createQuery("SELECT e FROM Employee e")
.getResultList();
} catch (Exception e) {
throw new EJBException(e.getMessage());
}
return employees;
}
}
为了测试这个会话Bean,可以创建一个Servlet来调用其getEmployees方法:
// JpqlTestServlet.java
package ejbjpa.servlets;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.ejb.EJB;
import java.util.List;
import java.util.Iterator;
import ejbjpa.entities.*;
import ejbjpa.ejb.*;
public class JpqlTestServlet extends HttpServlet {
@EJB private JpqlTest jpqlTest;
public void doGet(
HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.setBufferSize(8192);
PrintWriter out = response.getWriter();
try{
List<Employee> employees = jpqlTest.getEmployees();
Iterator i = employees.iterator();
Employee employee;
while (i.hasNext()) {
employee = (Employee) i.next();
out.println("Employee id: "+ employee.getEmpno() +"<br/>");
out.println("First name: "+ employee.getFirstname() +"<br/>");
out.println("Last name: "+ employee.getLastname() +"<br/>");
out.println("----------"+ "<br/>");
}
}
catch (Exception e){
e.printStackTrace();
}
}
}
6.2 检索的实体是否被管理
根据JPA规范,在事务上下文中执行查询时,无论以何种方式检索实体,它们都会自动附加到当前持久化上下文。可以通过以下方法进行测试:
// JpqlTestBean.java 增加 checkIfManaged 方法
import java.util.Iterator;
@Stateless
public class JpqlTestBean implements JpqlTest {
@PersistenceContext
private EntityManager em;
public List<Employee> getEmployees() {
// ...
}
public boolean checkIfManaged(){
List<Employee> employees = null;
try {
employees = (List<Employee>)em.createQuery("SELECT e FROM Employee e")
.getResultList();
Employee employee;
Iterator i = employees.iterator();
while (i.hasNext()) {
employee = (Employee) i.next();
if (!em.contains(employee)) {
return false;
}
}
} catch (Exception e) {
throw new EJBException(e.getMessage());
}
return true;
}
}
同时更新Servlet来调用这个方法:
// JpqlTestServlet.java 更新
public class JpqlTestServlet extends HttpServlet {
@EJB private JpqlTest jpqlTest;
public void doGet(
HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// ...
try{
// ...
out.println("All employee entities were managed during the " +
"checkIfManaged call: "+ jpqlTest.checkIfManaged() +"<br/>");
}
catch (Exception e){
e.printStackTrace();
}
}
}
6.3 导航检索实体的关系
JPQL查询返回的实体实例自动被管理,那么它们的关联实体呢?实际上,关联实体是否随查询结果一起被检索,取决于实体之间关系的获取模式(fetch mode)。默认情况下,一对多和多对多关系的获取模式为LAZY,一对一和多对一关系的获取模式为EAGER。
以下示例展示了关联实体在被访问时会自动被管理:
// JpqlTestBean.java 更新 checkIfManaged 方法
@Stateless
public class JpqlTestBean implements JpqlTest {
@PersistenceContext
private EntityManager em;
public List<Employee> getEmployees() {
// ...
}
public boolean checkIfManaged(){
List<Employee> employees = null;
List<Order> orders =null;
try {
employees = (List<Employee>)em.createQuery("SELECT e FROM Employee e")
.getResultList();
Employee employee;
Order order;
Iterator i = employees.iterator();
Iterator j;
while (i.hasNext()) {
employee = (Employee) i.next();
if (!em.contains(employee)) {
return false;
}
orders = (List<Order>)employee.getOrders();
j= orders.iterator();
while (j.hasNext()) {
order = (Order) j.next();
if (!em.contains(order)) {
return false;
}
}
}
} catch (Exception e) {
throw new EJBException(e.getMessage());
}
return true;
}
}
6.4 使用JPQL FETCH JOINS
当使用LAZY模式时,关联实体不会随查询结果一起被检索。有时,我们希望在查询结果中同时检索关联实体,这时可以使用JPQL FETCH JOINS。
以下是一个示例,创建一个JpqlJoinsTestBean会话Bean,其countOrders业务方法用于执行测试:
// JpqlJoinsTestBean.java
package ejbjpa.ejb;
import java.io.Serializable;
import javax.ejb.EJBException;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import java.util.List;
import ejbjpa.entities.*;
@Stateless
public class JpqlJoinsTestBean implements JpqlJoinsTest {
@PersistenceUnit(unitName="jpqljoins0-pu")
private EntityManagerFactory emf0;
@PersistenceUnit(unitName="jpqljoins1-pu")
private EntityManagerFactory emf1;
@PersistenceUnit(unitName="jpqljoins2-pu")
private EntityManagerFactory emf2;
public Integer[] countOrders(Integer empno){
List<Order> orders0 = null;
List<Order> orders1 = null;
List<Order> orders2 = null;
EntityManager em0 = emf0.createEntityManager();
EntityManager em1 = emf1.createEntityManager();
EntityManager em2 = emf2.createEntityManager();
Integer[] numOfOrders= new Integer[3];
try {
// 执行查询
Employee employee0 = (Employee)em0.createQuery("SELECT e " +
"FROM Employee e WHERE e.empno=:empno")
.setHint("toplink.refresh", "true")
.setParameter("empno", empno)
.getSingleResult();
Employee employee1 = (Employee)em1.createQuery("SELECT DISTINCT e " +
"FROM Employee e LEFT JOIN FETCH e.orders WHERE e.empno=:empno")
.setHint("toplink.refresh", "true")
.setParameter("empno", empno)
.getSingleResult();
Employee employee2 = (Employee)em2.createQuery("SELECT e " +
"FROM Employee e WHERE e.empno=:empno")
.setHint("toplink.refresh", "true")
.setParameter("empno", empno)
.getSingleResult();
// 统计第一个查询中员工的订单数量
orders0 = (List<Order>)employee0.getOrders();
numOfOrders[0] = orders0.size();
// 为第一个查询中的员工增加一个订单并提交更改
Order order = new Order();
order.setEmployee(employee0);
em0.getTransaction().begin();
em0.persist(order);
em0.getTransaction().commit();
// 统计第二个和第三个查询中员工的订单数量
orders1 = (List<Order>)employee1.getOrders();
numOfOrders[1] = orders1.size();
orders2 = (List<Order>)employee2.getOrders();
numOfOrders[2] = orders2.size();
} catch (Exception e) {
throw new EJBException(e.getMessage());
}
em0.close();
em1.close();
em2.close();
return numOfOrders;
}
}
为了测试这个会话Bean,可以创建一个Servlet:
// JpqlJoinsTestServlet.java
package ejbjpa.servlets;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.ejb.EJB;
import ejbjpa.entities.*;
import ejbjpa.ejb.*;
public class JpqlJoinsTestServlet extends HttpServlet {
@EJB private JpqlJoinsTest jpqlJoinsTest;
public void doGet(
HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
response.setBufferSize(8192);
PrintWriter out = response.getWriter();
Integer[] numOfOrders= new Integer[3];
numOfOrders=jpqlJoinsTest.countOrders(1);
try{
out.println("Number of orders associated with employee 1 before " +
"increasing: "+numOfOrders[0] +"<br/>");
out.println("Number of orders returned by the JOIN query after " +
"increasing: "+numOfOrders[1] +"<br/>");
out.println("Number of orders you actually got after " +
"increasing: "+numOfOrders[2] +"<br/>");
}
catch (Exception e){
e.printStackTrace();
}
}
}
执行该Servlet时,输出结果可能如下:
Number of orders associated with employee 1 before increasing: 25
Number of orders returned by the JOIN query after increasing: 25
Number of orders you actually got after increasing: 26
通常,前两个数字应该相等,最后一个数字应该比前两个数字大1。
7. 使用原生SQL查询
在开发使用JPA的Java应用程序时,仍然可以使用原生SQL表达查询。当需要利用JPQL无法实现的数据库特定功能时,选择SQL是一个不错的选择。
7.1 处理原生SQL查询
与JPQL类似,使用EntityManager的特殊方法来创建用原生SQL表达的查询。以下是相关方法:
| 方法 | 描述 |
| — | — |
| public Query createNamedQuery(String query_name) | 创建一个在元数据中定义的静态查询,使用NamedQuery注解或NamedNativeQuery注解应用于实体,并返回一个用于执行该命名查询的Query实例。 |
| public Query createNativeQuery(String sql_stmt) | 创建一个在业务逻辑代码中定义的动态查询,接受一个原生SQL语句(UPDATE或DELETE)作为参数,并返回一个用于执行该SQL语句的Query实例。 |
| public Query createNativeQuery(String sql_stmt, Class result_class) | 创建一个在业务逻辑代码中定义的动态查询,接受两个参数:一个SELECT SQL语句和结果实例的实体类,并返回一个用于执行指定SQL语句的Query实例。 |
| public Query createNativeQuery(String sql_stmt, String result_SetMapping) | 创建一个在业务逻辑代码中定义的动态查询,接受两个参数:一个SELECT SQL语句和使用SqlResultSetMapping注解在元数据中定义的结果集映射的名称,并返回一个用于执行指定SQL语句的Query实例。 |
7.2 原生SQL查询的简单示例
以下是一个在无状态会话Bean中使用动态原生SQL查询的简单示例:
// NativeQueryTestBean.java
package ejbjpa.ejb;
import java.io.Serializable;
import javax.ejb.EJBException;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import ejbjpa.entities.*;
@Stateless
public class NativeQueryTestBean implements NativeQueryTest {
@PersistenceContext
private EntityManager em;
public List<Employee> getEmployees() {
List<Employee> employees = null;
try {
employees = (List<Employee>)em.createNativeQuery("SELECT * FROM " +
"employees", ejbjpa.entities.Employee.class)
.getResultList();
} catch (Exception e) {
throw new EJBException(e.getMessage());
}
return employees;
}
}
测试这个会话Bean时,可以使用与测试JpqlTestBean会话Bean相同的Servlet,只需更改使用@EJB注解注入的Bean名称。
总结
JPQL为访问和操作实体实例提供了强大的方式,它是一种与数据库供应商无关的语言,无论实体映射到何种底层数据库都能正常工作。但当需要利用数据库特定功能时,仍然可以使用原生SQL对JPA实体创建和执行查询。通过掌握JPQL和原生SQL的使用,我们可以更灵活地开发使用JPA的Java应用程序。
Java持久化查询语言(JPQL)的使用指南
8. JPQL与原生SQL的选择决策流程
在实际开发中,如何选择使用JPQL还是原生SQL是一个关键问题。下面通过一个mermaid流程图来展示选择的决策流程:
graph TD;
A[是否需要数据库特定功能?] -->|是| B[选择原生SQL];
A -->|否| C[是否需要检索实体集合或执行连接查询等通用操作?];
C -->|是| D[选择JPQL];
C -->|否| E[根据具体情况再评估];
这个流程图清晰地展示了决策的步骤:首先判断是否需要数据库特定功能,如果需要则选择原生SQL;如果不需要,再看是否有检索实体集合或执行连接查询等通用操作的需求,若有则选择JPQL,若没有则需要根据具体情况进一步评估。
9. 实际应用中的注意事项
在使用JPQL和原生SQL进行开发时,还有一些实际应用中的注意事项需要我们关注:
-
事务管理
:无论是JPQL还是原生SQL查询,在涉及数据修改(如UPDATE、DELETE操作)时,都需要确保在事务上下文中执行。例如,在前面的示例中,为员工增加订单并提交更改的操作就需要在事务中进行:
em0.getTransaction().begin();
em0.persist(order);
em0.getTransaction().commit();
- 异常处理 :在执行查询时,可能会出现各种异常,如数据库连接异常、查询语法错误等。因此,需要对异常进行适当的处理,避免程序崩溃。在前面的示例中,我们使用了try-catch块来捕获异常并抛出EJBException:
try {
// 查询操作
} catch (Exception e) {
throw new EJBException(e.getMessage());
}
- 性能优化 :对于复杂的查询,尤其是涉及大量数据的查询,需要考虑性能优化。可以通过合理使用索引、优化查询语句、避免不必要的关联查询等方式来提高查询性能。例如,在使用JPQL的FETCH JOIN时,可以一次性检索关联实体,减少后续的数据库查询次数。
10. 综合示例:一个完整的业务场景
假设我们有一个电商系统,需要查询某个客户的订单信息,并根据订单金额进行分组统计。以下是一个综合使用JPQL的示例:
// CustomerOrderService.java
package com.example.service;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Stateless
public class CustomerOrderService {
@PersistenceContext
private EntityManager em;
public List<Object[]> getCustomerOrderStatistics(Long customerId) {
String jpql = "SELECT o.status, SUM(o.amount) " +
"FROM Order o " +
"JOIN o.customer c " +
"WHERE c.id = :customerId " +
"GROUP BY o.status";
return em.createQuery(jpql)
.setParameter("customerId", customerId)
.getResultList();
}
}
// CustomerOrderServlet.java
package com.example.servlets;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@WebServlet("/customerOrderStatistics")
public class CustomerOrderServlet extends HttpServlet {
@EJB
private CustomerOrderService customerOrderService;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
Long customerId = Long.parseLong(request.getParameter("customerId"));
List<Object[]> statistics = customerOrderService.getCustomerOrderStatistics(customerId);
out.println("<html><body>");
out.println("<h2>Customer Order Statistics</h2>");
out.println("<table border='1'>");
out.println("<tr><th>Order Status</th><th>Total Amount</th></tr>");
for (Object[] row : statistics) {
String status = (String) row[0];
Double totalAmount = (Double) row[1];
out.println("<tr><td>" + status + "</td><td>" + totalAmount + "</td></tr>");
}
out.println("</table>");
out.println("</body></html>");
}
}
在这个示例中,
CustomerOrderService
类中的
getCustomerOrderStatistics
方法使用JPQL查询某个客户的订单信息,并根据订单状态进行分组统计。
CustomerOrderServlet
类则负责接收客户端请求,调用服务方法并将结果以HTML表格的形式展示给用户。
11. 总结与展望
通过前面的介绍,我们详细了解了Java持久化查询语言(JPQL)和原生SQL的使用方法、适用场景以及实际应用中的注意事项。JPQL为我们提供了一种与数据库无关的方式来操作实体,使得我们的代码具有更好的可移植性;而原生SQL则在需要利用数据库特定功能时发挥重要作用。
在未来的开发中,随着数据库技术和Java Persistence API的不断发展,我们可以期待更多的功能和优化。例如,可能会有更智能的查询优化器,能够自动根据数据库的特性和查询需求生成最优的查询语句;也可能会有更方便的工具来帮助我们进行事务管理和异常处理。我们需要不断学习和掌握这些新技术,以提高我们的开发效率和应用程序的性能。
同时,我们也应该注意代码的可维护性和可读性。在编写查询语句时,尽量遵循良好的编程规范,使用有意义的变量名和注释,使得代码易于理解和维护。通过合理地使用JPQL和原生SQL,我们可以构建出更加高效、稳定和可扩展的Java应用程序。
超级会员免费看
51

被折叠的 条评论
为什么被折叠?



