跳转到内容

Java 持久性/查询

来自 Wikibooks,开放世界中的开放书籍

查询是持久性的基本组成部分。如果无法查询数据,那么持久化数据就没有什么用处。有很多查询语言和框架;最常见的查询语言是关系型数据库中使用的 SQL。

JPA 提供了几种查询机制

JPA 主要使用 Java 持久性查询语言 (JPQL),该语言基于 SQL 语言,并从 EJB 查询语言 (EJBQL) 演变而来。它基本上在对象级别而不是数据级别提供 SQL 语法。JPQL 在语法上类似于 SQL,可以通过其BNF 定义来定义。

JPA 还提供 Criteria API,允许使用 Java API 轻松构建动态查询。Criteria API 镜像 JPQL 语法,但为每个操作/函数提供 Java API,而不是使用单独的查询语言。

JPA 通过 Query 接口和 @NamedQuery@NamedNativeQuery 注解以及 <named-query><named-native-query> XML 元素提供查询功能。

其他查询语言和框架包括

命名查询

[编辑 | 编辑源代码]

JPA 中主要有两种类型的查询:命名查询和动态查询。命名查询用于应用程序中多次使用的静态查询。命名查询的优点是它可以在一个地方定义一次,并在应用程序中重复使用。大多数 JPA 提供程序还会预解析/编译命名查询,因此它们比动态查询更优化,动态查询通常必须在每次执行时解析/编译。由于命名查询是持久性元数据的组成部分,因此它们也可以在 orm.xml 中进行优化或覆盖,而无需更改应用程序代码。

命名查询通过 @NamedQuery@NamedQueries 注解或 <named-query> XML 元素来定义。命名查询通过 EntityManager.createNamedQuery API 访问,并通过 Query 接口执行。

命名查询可以在任何带注解的类上定义,但通常定义在它们查询的 Entity 上。命名查询的名称对于整个持久性单元必须是唯一的,名称不是 Entity 本地的。在 orm.xml 中,命名查询可以在 <entity-mappings> 或任何 <entity> 上定义。

命名查询通常是参数化的,因此它们可以使用不同的参数值执行。参数在 JPQL 中使用 :<name> 语法来定义命名参数,或使用 ? 语法来定义位置参数。

还可以向命名查询提供查询提示集合。查询提示可用于优化或向查询提供特殊配置。查询提示特定于 JPA 提供程序。查询提示通过 @QueryHint 注解或 query-hint XML 元素来定义。

命名查询注解示例

[编辑 | 编辑源代码]
@NamedQuery(
  name="findAllEmployeesInCity",
  query="Select emp from Employee emp where emp.address.city = :city"
  hints={@QueryHint(name="acme.jpa.batch", value="emp.address")}
)
public class Employee {
  ...
}

命名查询 XML 示例

[编辑 | 编辑源代码]
<entity-mappings>
  <entity name="Employee" class="org.acme.Employee" access="FIELD">
    <named-query name="findAllEmployeesInCity">
      <query>Select emp from Employee emp where emp.address.city = :city</query>
      <hint name="acme.jpa.batch" value="emp.address"/>
    </named-query>
    <attributes>
        <id name="id"/>
    </attributes>
  </entity>
</entity-mappings>

命名查询执行示例

[编辑 | 编辑源代码]
EntityManager em = getEntityManager();
Query query = em.createNamedQuery("findAllEmployeesInCity");
query.setParameter("city", "Ottawa");
List<Employee> employees = query.getResultList();
...

动态查询

[编辑 | 编辑源代码]

动态查询通常用于查询取决于上下文的情况。例如,取决于查询表单中哪些项目已填写,查询可能具有不同的参数。动态查询对于不常见查询或原型设计也很有用。

JPA 为动态查询提供了两种主要选择:JPQL 和 Criteria API。

动态查询可以使用参数和查询提示,与命名查询相同。

动态查询通过 EntityManager.createQuery API 访问,并通过 Query 接口执行。

动态查询执行示例

[编辑 | 编辑源代码]
EntityManager em = getEntityManager();
Query query = em.createQuery("Select emp from Employee emp where emp.address.city = :city");
query.setParameter("city", "Ottawa");
query.setHint("acme.jpa.batch", "emp.address");
List<Employee> employees = query.getResultList();
...

Criteria API (JPA 2.0)

[编辑 | 编辑源代码]

参见 Criteria API.

参见JPQL.

参数

[edit | edit source]

参数在 JPQL 中使用 :<param> 语法定义,例如 "Select e from Employee e where e.id = :id"。参数值使用 QueryQuery.setParameter API 设置。

参数也可以使用 ? 定义,主要用于原生 SQL 查询。你也可以使用 ?<int>。这些是位置参数,而不是命名参数,使用 Query API Query.setParameter 设置。int 是参数在 SQL 中的索引。位置参数从 1 开始(不是 0)。一些 JPA 提供程序也允许在原生查询中使用 :<param> 语法。

对于时间参数 (Date, Calendar),你也可以传递时间类型,具体取决于你是否要从值中获取 Date, TimeTimestamp

参数通常是基本值,但你也可以引用对象,如果它们在 Id 上进行比较,例如,"Select e from Employee e where e.address = :address" 可以使用 Address 对象作为参数。参数值在与映射属性比较时始终在对象级别,例如,如果与映射的 enum 进行比较,则使用 enum 值,而不是数据库值。

参数始终在 Query 上设置,无论它是什么类型的查询(JPQL、Criteria、原生 SQL、NamedQuery)。

命名参数

Query query = em.createQuery("Select e from Employee e where e.name = :name");
query.setParameter("name", "Bob Smith");

位置参数

Query query = em.createNativeQuery("SELECT * FROM EMPLOYEE WHERE NAME = ?");
query.setParameter(1, "Bob Smith");

查询结果

[edit | edit source]

通常,JPA 查询返回你的持久 Entity 对象。返回的对象将由持久上下文 (EntityManager) 管理,对对象的更改将作为当前事务的一部分进行跟踪。在某些情况下,可以构建更复杂的查询,这些查询只返回数据而不是 Entity 对象,甚至执行 updatedeletion 操作。

有三种方法可以执行 Query,每种方法都返回不同的结果

getResultList 返回结果的 List。这通常是 Entity 对象的 List,但也可能是数据的列表或数组。

JPQL / SQL结果
SELECT e FROM Employee e这将返回一个 List<Employee>(Employee 对象的列表)。这些对象将被管理。
SELECT e.firstName FROM Employee e这将返回一个 List<String>(String 值的列表)。数据未被管理。
SELECT e.firstName, e.lastName FROM Employee e这将返回一个 List<Object[String, String]>(每个包含两个 String 值的对象数组的列表)。数据未被管理。
SELECT e, e.address FROM Employee e这将返回一个 List<Object[Employee, Address]>(每个包含 Employee 和 Address 对象的对象数组的列表)。这些对象将被管理。
SELECT EMP_ID, F_NAME, L_NAME FROM EMP这将返回一个 List<Object[BigDecimal, String, String]>(每个包含行数据的对象数组的列表)。数据未被管理。


getSingleResult 返回结果。这通常是一个 Entity 对象,但也可能是数据或对象数组。如果查询没有返回任何结果,则会抛出异常。这很不幸,因为通常只是返回 null 是想要的。一些 JPA 提供程序可能提供一个选项,如果未返回任何结果,则返回 null 而不是抛出异常。如果查询返回的不仅仅是一行,也会抛出异常。这也很不幸,因为通常只是返回第一个结果是想要的。一些 JPA 提供程序可能提供一个选项,返回第一个结果而不是抛出异常,否则你需要调用 getResultList 并获取第一个元素。

JPQL / SQL结果
SELECT e FROM Employee e这将返回一个 Employee。该对象将被管理。
SELECT e.firstName FROM Employee e这将返回一个 String。数据未被管理。
SELECT e.firstName, e.lastName FROM Employee e这将返回一个 Object[String, String](包含两个 String 值的对象数组)。数据未被管理。
SELECT e, e.address FROM Employee e这将返回一个 Object[Employee, Address](包含 Employee 和 Address 对象的对象数组)。这些对象将被管理。
SELECT EMP_ID, F_NAME, L_NAME FROM EMP这将返回一个 Object[BigDecimal, String, String](包含行数据的对象数组)。数据未被管理。


executeUpdate 返回数据库行计数。这可用于 UPDATE DELETE JPQL 查询,或任何不返回结果的原生 SQL (DML 或 DDL) 查询。

常用查询

[edit | edit source]

连接,查询一对多关系

[edit | edit source]

要查询 所有电话号码区号为 613 的员工,需要使用 连接

JPQL

SELECT e FROM Employee e JOIN e.phoneNumbers p where p.areaCode = '613'

Criteria

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> query = cb.createQuery(Employee.class);
Root<Employee> employee = query.from(Employee.class);
Join<PhoneNumber> phone = employee.join("phoneNumbers");
query.where(cb.equal(phone.get("areaCode"), "613"));

子查询,查询多对多关系中的所有关系

[edit | edit source]

要查询 所有项目都处于困难状态的员工,需要使用带有双重否定的 子查询

JPQL

SELECT e FROM Employee e JOIN e.projects p where NOT EXISTS (SELECT t from Project t where p = t AND t.status <> 'In trouble')

Criteria

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> query = cb.createQuery(Employee.class);
Root<Employee> employee = query.from(Employee.class);
Join<Project> project = employee.join("projects");
Subquery<Project> subquery = query.subquery(Project.class);
Root<Project> subProject = query.from(Project.class);
subquery.where(cb.and(cb.equal(project, subProject), cb.equal(subProject.get("status"), "In trouble")));
query.where(cb.not(cb.exists(subquery));

子查询,查询多对多关系,其中所有关系都在列表中

[edit | edit source]

要查询 所有参与所有项目列表的员工,需要使用带有计数的 子查询。首先从项目中收集项目 ID(使用对象也可以)。然后获取列表的大小。你的查询将需要两个参数,一个是 ID 列表,另一个是列表的大小。

JPQL

SELECT e FROM Employee e where :size = (SELECT COUNT(p) from Project p, Employee e2 join e2.projects p2 where p = p2 AND e = e2 AND p.id IN :projects)

连接获取,在同一查询中读取员工和地址

[edit | edit source]

要查询 所有员工及其地址,需要使用 连接获取。这将在同一查询中选择员工和地址数据。如果没有使用连接获取,则员工地址仍然可用,但可能会导致对每个员工及其地址的查询。这将 n+1 个查询减少到 1 个查询。

连接获取

SELECT e FROM Employee e JOIN FETCH e.address

连接获取也可以用于集合关系

SELECT e FROM Employee e JOIN FETCH e.address JOIN FETCH e.phones

外连接可用于避免 null 和空关系过滤结果

SELECT e FROM Employee e LEFT OUTER JOIN FETCH e.address LEFT OUTER JOIN FETCH e.phones

你也可以在一个查询中选择多个对象,但请注意,这不会实例化关系,因此访问关系仍然可能触发另一个查询

SELECT e, a FROM Employee e, Address a WHERE e.address = a

反向多对多,给定项目的全部员工

[edit | edit source]

要查询 给定项目的全部员工,其中员工项目关系为多对多。

如果关系是双向的,你可以使用

Select p.employees from Project p where p.name = :name

如果它是单向的,你可以使用

Select e from Employee e, Project p where p.name = :name and p member of e.projects

或者

Select e from Employee e join e.projects p where p.name = :name

如何模拟强制转换为子类

[edit | edit source]

要查询 所有拥有预算超过 1,000,000 的大型项目的员工,其中员工只与 Project 有关系,而不是与 LargeProject 子类有关系。JPA 1.0 JPQL 没有定义强制转换操作(JPA 2.0 可能定义了它),因此在子类的属性上进行查询并不明显。但是,如果将子类的二级连接添加到查询中,则可以间接完成此操作。

Select e from Employee e join e.projects p, LargeProject lproject where p = lproject and lproject.budget > 1000000

如何选择集合中的第一个元素

[edit | edit source]

要查询 特定员工的第一个项目。有几种不同的方法可以做到这一点,有些方法使用直接的 JPQL,有些方法使用 QuerysetMaxResuls API。如果 JPA 2.0 索引列表用于映射集合,则可以使用 INDEX 函数。

setMaxResults

Query query = em.createQuery("Select e.projects from Employee e where e.id = :id");
query.setMaxResults(1);


Query query = em.createQuery("Select p from Employee e join e.projects p where e.id = :id");
query.setMaxResults(1);

JPQL

Select p from Project p where p.id = (Select MAX(p2.id) from Employee e join e.projects p2 where e.id = :id)

JPA 2.0

Select p from Employee e join e.projects p where e.id = :id and INDEX(p) = 1

如何按集合的大小排序

[edit | edit source]

要查询按项目数量排序的所有员工。 有几种不同的方法可以做到这一点,一些方法最终在 SQL 中使用子查询,另一些方法使用 group by。 具体取决于您的 JPA 提供程序和数据库,您的解决方案可能仅限于其中之一。

使用 SIZE 函数(在 SQL 中使用子查询)

Select e from Employee order by SIZE(e.projects) DESC

使用 SIZE 函数,也选择大小(使用 group by)

Select e, SIZE(e.projects) from Employee order by SIZE(e.projects) DESC

使用 GROUP BY

Select e, COUNT(p) from Employee join e.projects p order by COUNT(p) DESC

使用 GROUP BY 和别名

Select e, COUNT(p) as pcount from Employee join e.projects p order by pcount DESC

连接获取和查询优化

[编辑 | 编辑源代码]

在 JPA 中有几种优化查询的方法。 典型的查询性能问题是首先读取对象,然后逐个读取其相关对象。 这可以使用 JPQL 中的JOIN FETCH进行优化,否则使用特定于每个 JPA 提供程序的查询提示进行优化。

参见:

超时、获取大小和其他 JDBC 优化

[编辑 | 编辑源代码]

执行查询时可以使用多个 JDBC 选项。 这些 JDBC 选项不会由 JPA 公开,但一些 JPA 提供程序可能支持针对它们的查询提示。

  • 获取大小:配置从数据库中每页获取的行数。 对于大型查询,更大的获取大小效率更高。
  • 超时:指示数据库在查询执行时间过长时取消查询。


EclipseLink/TopLink:提供许多查询提示,包括
"eclipselink.jdbc.fetch-size" - 获取大小。
"eclipselink.jdbc.timeout" - 超时。
"eclipselink.read-only" - 从查询返回的对象不受持久上下文管理,也不跟踪更改。
"eclipselink.query-type" - 定义要用于查询的查询的本机类型。
"eclipselink.sql.hint" - 允许在查询的 SQL 中包含 SQL 提示。
"eclipselink.jdbc.bind-parameters" - 指定是否应使用参数绑定(默认情况下使用)。

更新和删除查询

[编辑 | 编辑源代码]

JPQL 还允许执行UPDATEDELETE查询。 这不是在 JPA 中修改对象的推荐或正常方式。 通常在 JPA 中,您首先读取对象,然后直接使用其set方法修改它以更新它,或者调用EntityManager.remove()方法删除它。

JPQL 中的UPDATEDELETE查询用于执行批量更新或删除。 它们允许在单个查询中更新或删除一组对象。 这些查询对于执行批量操作或清除测试数据很有用。

UPDATEDELETE查询与SELECT查询具有相同的WHERE,可以使用相同的函数和操作,并遍历关系并使用子查询UPDATEDELETE查询使用Query.executeUpdate()方法执行,并返回来自数据库的行数。 请注意,在活动持久上下文中执行这些查询时应谨慎使用,因为查询可能会影响已在EntityManager中注册的对象。 通常最好在执行查询后clear()EntityManager,或者在新的EntityManager或事务中执行查询。

示例更新查询

[编辑 | 编辑源代码]
UPDATE Employee e SET e.salary = e.salary + 1000 WHERE e.address.city = :city

示例删除查询

[编辑 | 编辑源代码]
DELETE FROM Employee e WHERE e.address.city = :city

刷新模式

[编辑 | 编辑源代码]

在 JPA 中的事务上下文中,对受管对象的更改通常不会在提交之前刷新(写入)到数据库。 因此,如果直接针对数据库执行查询,它将看不到事务中进行的更改,因为这些更改仅在 Java 中的内存中进行。 如果已持久化新对象,或者对象已被删除或更改,这可能会导致问题,因为应用程序可能希望查询返回这些结果。 由于这种原因,JPA 要求 JPA 提供程序在任何查询操作之前执行对数据库的所有更改的刷新。 但是,如果应用程序没有期望刷新作为查询操作的副作用,这可能会导致问题。 刷新也可能很昂贵,并且会导致数据库事务以及数据库锁和其他资源在事务持续时间内被持有,这会影响性能和并发。

JPA 允许使用FlushModeType枚举和Query.setFlushMode()API 配置查询的刷新模式。 刷新模式是AUTO(默认值,表示在每次查询执行之前刷新)或COMMIT(表示仅在提交时刷新)。 也可以使用EntityManager.setFlushMode()API 在EntityManager上设置刷新模式,以影响使用EntityManager执行的所有查询。 可以随时在EntityManager上直接调用EntityManager.flush()API 以执行所需刷新。

一些 JPA 提供程序还允许通过持久性单元属性配置刷新模式,或提供刷新替代方案,例如针对内存中对象执行查询。

TopLink / EclipseLink:允许使用持久性单元属性"eclipselink.persistence-context.flush-mode"="COMMIT"禁用自动刷新。

分页、最大/第一个结果

[编辑 | 编辑源代码]

一个常见的需求是允许用户浏览大型查询结果。 通常,在查询执行后,Web 用户会获得前n条结果的第一页,并且可以点击下一页转到下一页,或点击上一页返回。

如果您不关心性能,或者结果不太大,实现此功能的最简单方法是查询所有结果,然后从结果列表中访问子列表以填充您的页面。 但是,您将不得不每次页面请求都重新查询所有结果。

一个简单的解决方案是将查询结果存储在有状态SessionBeanhttp 会话中。 这意味着初始查询可能需要一段时间,但分页会很快。 一些 JPA 提供程序还支持查询结果的缓存,因此您可以将结果缓存到 JPA 提供程序的缓存中,只需重新执行查询即可获得缓存的结果。

如果查询结果非常大,则可能需要其他解决方案。 JPA 提供了Query API setFirstResultsetMaxResults,以允许浏览大型查询结果。 maxResults也可以用作保障措施,以避免让用户执行返回太多对象的查询。

这些查询属性的实现方式取决于 JPA 提供程序和数据库。 JDBC 允许设置maxResults,并且大多数 JDBC 驱动程序都支持此功能,因此它通常适用于大多数 JPA 提供程序和大多数数据库。 firstResult的支持效率可能无法得到保证,因为它通常需要数据库特定的 SQL。 没有用于分页的标准 SQL,因此是否支持它取决于您的数据库以及 JPA 提供程序的支持。

执行分页时,还必须对结果进行排序。 如果查询没有对结果进行排序,则每个后续查询都有可能以不同的顺序返回结果,并提供不同的页面。 此外,如果在查询之间插入/删除行,则结果可能略有不同。

使用 firstResult、maxResults 的示例

[edit | edit source]
Query query = em.createQuery("Select e from Employee e order by e.id");
query.setFirstResult(100);
query.setMaxResults(200);
List<Employee> page = query.getResultList();


除了使用firstResult,您还可以根据排序规则和上一页的值在 where 子句中过滤第一个结果。

使用 maxResults 和 order by 的示例

[edit | edit source]
Query query = em.createQuery("Select e from Employee e where e.id > :lastId order by e.id");
query.setParameter("lastId", previousPage.get(previousPage.size()-1).getId());
query.setMaxResults(100);
List<Employee> nextPage = query.getResultList();


另一种方法是只查询Id,并将结果存储在状态化SessionBeanhttp session中。然后,您可以针对每页查询一组Id

使用 Ids 和 IN 的示例

[edit | edit source]
Query query = em.createQuery("Select e.id from Employee e");
List<Long> ids= query.getResultList();

Query pageQuery = em.createQuery("Select e from Employee e where e.id in :ids");
pageQuery.setParameter("ids", ids.subList(100, 200));
List<Employee> page = pageQuery.getResultList();


分页还可以用于服务器进程或批处理作业。在服务器上,通常使用分页来避免预先使用太多内存,并允许一次处理一个批次。您可以使用任何这些技术,一些 JPA 提供程序还支持返回数据库游标来进行查询结果,允许您滚动浏览结果。

TopLink / EclipseLink : 通过查询提示"eclipselink.cursor.scrollable""eclipselink.cursor" 以及 CursoredStreamScrollableCursor 类支持流和可滚动游标。

本地 SQL 查询

[edit | edit source]

通常,JPA 中的查询是通过 JPQL 定义的。JPQL 允许您根据对象模型而不是数据模型来定义查询。由于开发人员使用对象模型在 Java 中编程,因此这通常更直观。这也允许数据抽象以及数据库架构和数据库平台独立性。JPQL 支持大部分 SQL 语法,但某些方面的 SQL 或者特定的数据库扩展或函数可能无法通过 JPQL 实现,因此有时需要使用原生 SQL 查询。此外,一些开发人员对 SQL 比对 JPQL 更熟悉,因此可能更喜欢使用 SQL 查询。原生查询还可以用于调用某些类型的存储过程或执行 DML 或 DDL 操作。

原生查询是通过 @NamedNativeQuery@NamedNativeQueries 注解或 <named-native-query> XML 元素来定义的。还可以使用 EntityManager.createNativeQuery() API 动态定义原生查询。

原生查询可以用于查询类实例、查询原始数据、更新或 DML 或 DDL 操作,或查询复杂查询结果。如果查询针对的是类,则必须设置查询的 resultClass 属性。如果查询结果很复杂,则可以使用 结果集映射

原生查询可以参数化,因此可以使用不同的参数值执行。参数是在 SQL 中使用 ? 语法来定义位置参数的,JPA 不要求原生查询支持命名参数,但一些 JPA 提供程序可能支持。对于位置参数,位置从 1 开始(而不是 0)。

还可以为原生查询提供一组查询提示。查询提示可以用于优化或为查询提供特殊配置。查询提示特定于 JPA 提供程序。查询提示是通过 @QueryHint 注解或 query-hint XML 元素来定义的。

原生命名查询注解示例

[edit | edit source]
@NamedNativeQuery(
  name="findAllEmployeesInCity",
  query="SELECT E.* from EMP E, ADDRESS A WHERE E.EMP_ID = A.EMP_ID AND A.CITY = ?",
  resultClass=Employee.class
)
public class Employee {
  ...
}

原生命名查询 XML 示例

[edit | edit source]
<entity-mappings>
  <entity name="Employee" class="org.acme.Employee" access="FIELD">
    <named-native-query name="findAllEmployeesInCity" result-class="org.acme.Employee">
      <query>SELECT E.* from EMP E, ADDRESS A WHERE E.EMP_ID = A.EMP_ID AND A.CITY = ?</query>
    </named-native-query>
    <attributes>
        <id name="id"/>
    </attributes>
  </entity>
</entity-mappings>

原生命名查询执行示例

[edit | edit source]
EntityManager em = getEntityManager();
Query query = em.createNamedQuery("findAllEmployeesInCity");
query.setParameter(1, "Ottawa");
List<Employee> employees = query.getResultList();
...

动态原生查询执行示例

[edit | edit source]
EntityManager em = getEntityManager();
Query query = em.createNativeQuery("SELECT E.* from EMP E, ADDRESS A WHERE E.EMP_ID = A.EMP_ID AND A.CITY = ?", Employee.class);
query.setParameter(1, "Ottawa");
List<Employee> employees = query.getResultList();
...

结果集映射

[edit | edit source]

当原生 SQL 查询返回对象时,SQL 必须确保它返回正确的数据,以使用映射中指定的正确列名来构建 resultClass。如果 SQL 更复杂并返回不同的列名或返回多个对象的数据,则必须使用 @SqlResultSetMapping

@SqlResultSetMapping 是一个相当复杂的注解,它包含一个 @EntityResult@ConstructorResult@ColumnResult 数组。这允许将多个 Entity 对象与原始数据和未映射的类组合返回。@EntityResult 包含一个 @FieldResult 数组,它可以用于将 SQL 中使用的别名映射到映射所需的列名。如果需要返回同一个类的两个不同实例,或者如果 SQL 需要出于某种原因以不同的方式对列进行别名,则需要这样做。请注意,在 @FieldResult 中,name 是对象中属性的名称,而不是映射中的列名。这似乎很奇怪,因为这将使映射 Embedded 或组合 id 关系变得不可能。

通常,使用原生 SQL 查询选择原始数据或单个对象最容易,因此通常可以避免 @SqlResultSetMapping,因为它们相当复杂。另外请注意,即使您使用 SQL 选择 Employee 及其 Address,它们也是两个不相关的对象,员工的地址不会被设置,并且可能会触发查询,除非缓存命中。一些 JPA 提供程序可能会提供一个查询提示,以允许连接获取与原生 SQL 查询一起使用。

TopLink / EclipseLink : 通过 "eclipselink.join-fetch" 查询提示支持原生 SQL 查询的连接获取。

结果集映射注解示例

[edit | edit source]
@NamedNativeQuery(
  name="findAllEmployeesInCity",
  query="SELECT E.*, A.* from EMP E, ADDRESS A WHERE E.EMP_ID = A.EMP_ID AND A.CITY = ?",
  resultSetMapping="employee-address"
)
@SqlResultSetMapping(name="employee-address", 
  entities={ 
    @EntityResult(entityClass=Employee.class),
    @EntityResult(entityClass=Address.class)}
)
public class Employee {
  ...
}

ConstructorResult (JPA 2.1)

[edit | edit source]

JPA 2.1 定义了一个 @ConstructorResult 注解,以允许从原生 SQL 查询中返回未映射的类。ConstructorResult 类似于 JPQL NEW 运算符,它允许调用类构造函数并传递原始数据。ConstructorResult 具有一个 targetClass 和一个 columns 数组,该数组包含 ColumnResults。目标类必须定义一个构造函数,该构造函数接受与 columns 定义的相同数量的参数和类型。

构造函数结果注解示例

[edit | edit source]
@NamedNativeQuery(
  name="findAllEmployeeDetails",
  query="SELECT E.EMP_ID, E.F_NAME, E.L_NAME, A.CITY from EMP E, ADDRESS A WHERE E.EMP_ID = A.EMP_ID",
  resultSetMapping="employee-details"
)
@SqlResultSetMapping(name="employee-details", 
  classes={ 
    @ConstructorResult(targetClass=EmployeeDetails.class, columns={
        @ColumnResult(name="EMP_ID", type=Integer.class),
        @ColumnResult(name="F_NAME", type=String.class),
        @ColumnResult(name="L_NAME", type=String.class),
        @ColumnResult(name="CITY", type=String.class)
    })
  }
)
public class Employee {
  ...
}

存储过程

[edit | edit source]

参见 存储过程

原始 JDBC

[edit | edit source]

有时可能需要将 JDBC 代码与 JPA 代码混合使用。这可能是为了访问某些 JDBC 驱动程序特定功能,或为了与使用 JDBC 而不是 JPA 的另一个应用程序集成。

如果您只需要 JDBC 连接,您可以从 JEE 服务器的 DataSource 访问连接,或者直接连接到 DriverManager 或第三方连接池。如果您在同一个事务上下文中需要 JDBC 连接和 JPA 应用程序,则可以使用 JTA DataSource 用于 JPA 和您的 JDBC 访问,以使它们共享同一个全局事务。如果您没有使用 JEE 或没有使用 JTA,那么您可能能够直接从 JPA 提供程序访问 JDBC 连接。

一些 JPA 提供程序提供了一个 API,用于从其内部连接池或其事务上下文访问原始 JDBC 连接。在 JPA 2.0 中,这个 API 在 EntityManager 上的 unwrap API 中有所标准化。

要从 EntityManager 访问 JDBC 连接,一些 JPA 2.0 提供程序可能支持

java.sql.Connection connection = entityManager.unwrap(java.sql.Connection.class);

然后可以将此连接用于原始 JDBC 访问。通常在完成时不应关闭它,因为连接正在被 EntityManager 使用,并且将在 EntityManager 关闭或事务提交时释放。

TopLink / EclipseLink : 支持解包 JDBC Connection
华夏公益教科书