Hibernate.org社区文档

Hibernate EntityManager

用户指南


介绍 JPA 持久化
1. 架构
1.1. 定义
1.2. 在容器环境中(如 EJB 3)
1.2.1. 容器管理的实体管理器
1.2.2. 应用程序管理的实体管理器
1.2.3. 持久化上下文范围
1.2.4. 持久化上下文传播
1.3. Java SE 环境
2. 设置和配置
2.1. 设置
2.2. 配置和启动
2.2.1. 打包
2.2.2. 启动
2.3. 事件监听器
2.4. 在 Java SE 环境中获取 EntityManager
2.5. 其他
3. 使用对象
3.1. 实体状态
3.2. 使对象持久化
3.3. 加载对象
3.4. 查询对象
3.4.1. 执行查询
3.5. 修改持久化对象
3.6. 分离对象
3.7. 修改分离的对象
3.8. 自动状态检测
3.9. 删除受管对象
3.10. 强制刷新持久化上下文
3.10.1. 在事务中
3.10.2. 在事务外
3.11. 过渡持久化
3.12. 锁定
3.13. 缓存
3.14. 检查对象状态
3.15. 本机 Hibernate API
4. 元模型
4.1. 静态元模型
5. 事务和并发
5.1. 实体管理器和事务范围
5.1.1. 工作单元
5.1.2. 长工作单元
5.1.3. 考虑对象标识
5.1.4. 常见的并发控制问题
5.2. 数据库事务划分
5.2.1. 非托管环境
5.2.2. 使用 JTA
5.2.3. 异常处理
5.3. 扩展的持久化上下文
5.3.1. 容器管理实体管理器
5.3.2. 应用程序管理实体管理器
5.4. 乐观并发控制
5.4.1. 应用程序版本检查
5.4.2. 扩展的实体管理器和自动版本控制
5.4.3. 分离对象和自动版本控制
6. 实体监听器和回调方法
6.1. 定义
6.2. 回调和监听器的继承
6.3. XML 定义
7. 批处理
7.1. 批量更新/删除
8. JP-QL:对象查询语言
8.1. 大小写敏感
8.2. from 子句
8.3. 关联和连接
8.4. select 子句
8.5. 聚合函数
8.6. 多态查询
8.7. where 子句
8.8. 表达式
8.9. order by 子句
8.10. group by 子句
8.11. 子查询
8.12. JP-QL 示例
8.13. 批量 UPDATE 和 DELETE 语句
8.14. 提示和技巧
9. Criteria 查询
9.1. 类型化 Criteria 查询
9.1.1. 选择一个实体
9.1.2. 选择一个值
9.1.3. 选择多个值
9.1.4. 选择一个包装
9.2. Tuple Criteria 查询
9.2.1. 访问 tuple 元素
9.3. FROM 子句
9.3.1. 根
9.3.2. 连接
9.3.3. 获取
9.4. 路径表达式
9.5. 使用参数
10. 本机查询
10.1. 表达结果集
10.2. 使用本机 SQL 查询
10.3. 命名查询
参考

JPA 2 是 Java EE 6.0 平台的一部分。JPA 中的持久性可用于容器中,例如 EJB 3 或更现代的 CDI(Java 上下文和依赖项注入),以及在特定容器外部执行的独立 Java SE 应用程序中使用。以下编程界面和工件可在两种环境中使用。

实体管理器是用于与持久性上下文进行交互的 API。可以使用两种常见策略:将持久性上下文绑定到事务边界,或在多个事务中保持持久性上下文可用。

最常见的用例是将持久性上下文作用域绑定到当前事务作用域。这仅在使用 JTA 事务时才可行:持久性上下文与 JTA 事务生命周期相关联。调用实体管理器时,如果当前 JTA 事务未关联持久性上下文,则也会打开持久性上下文。否则,将使用关联的持久性上下文。JTA 事务完成后,持久性上下文结束。这意味着在 JTA 事务期间,应用程序将能够处理同一持久性上下文的受管实体。换而言之,您不必在受管 Bean (CDI) 或 EJB 方法调用中传递实体管理器的持久性上下文,只需在需要实体管理器时使用依赖关系注入或查找即可。

您还可以使用扩展持久性上下文。如果使用容器托管的实体管理器,则可以将其与有状态会话 Bean 结合使用:从依赖关系注入或 JNDI 查找检索实体管理器时会创建持久性上下文,并保留该上下文直至容器在 Remove 有状态会话 Bean 方法完成之后将其关闭。这是实现“长时间”工作单元模式的完美机制。例如,如果您必须将多个用户交互周期作为单个工作单元进行处理(例如,必须完全完成的向导对话框),则通常会将其建模为从应用程序用户的角度来看的工作单元,并使用扩展持久性上下文对其进行实现。有关该模式的更多信息,请参阅 Hibernate 参考手册或 Hibernate 一书。

JBoss Seam 3 建立在 CDI 之上,其核心概念包含对话和工作单元的观念。对于应用程序托管的实体管理器,会在创建实体管理器时创建持久性上下文,并保留该上下文直至关闭实体管理器为止。在扩展的持久性上下文中,在事务外部执行的所有修改操作(保持、合并、移除)都会排队,直至持久性上下文附加到事务。事务通常在用户进程结束时发生,允许整个进程提交或回滚。对于应用程序托管的实体管理器,仅支持扩展持久性上下文。

使用 EntityManagerFactory.createEntityManager()(应用程序托管)创建的资源本地实体管理器或实体管理器与持久性上下文具有单一对一关系。在其他情况下,会发生 持久性上下文传播

持久性上下文传播会针对容器托管的实体管理器发生。

在事务作用域容器中管理实体管理器 (Java EE 环境中的常见情况),JTA 事务传播与持久性上下文资源传播相同。换句话说,在给定 JTA 事务中检索到的容器管理事务作用域实体管理器都共享相同的持久性上下文。在 Hibernate 中,这意味着所有管理器共享相同的会话。

重要提示:持久性上下文从不在不同的 JTA 事务或不是由相同实体管理器工厂生成的实体管理器之间共享。在使用扩展持久性上下文进行上下文传播时,有一些值得注意的例外情况

JPA 2.0 兼容的 Hibernate EntityManager 是建立在 Hibernate 和 Hibernate Annotations 核心之上的。从 3.5 版本开始,我们已在一个 Hibernate 发行版中捆绑了所有必要的模块

下载 Hibernate Core 发行版。设置类路径(在你最喜欢的 IDE 中创建新项目后)

或者,如果你使用 Maven,则添加以下依赖关系


<project ...>
  ...
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>${hibernate-core-version}</version>
    </dependency>
  </dependencies>
</project>

所有必需的依赖项,例如 hibernate-core 和 hibernate-annotations,都会按传递方式拖动。

我们建议你使用 Hibernate Validator 和 Bean Validation 规范能力,因为其与 Java Persistence 2 的集成已标准化。从 Hibernate 网站下载 Hibernate Validator 4 或更高版本,并在你的类路径中添加 hibernate-validator.jarvalidation-api.jar。或者,在你的 pom.xml 中添加以下依赖项。


<project>
  ...
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>${hibernate-validator-version}</version>
    </dependency>
    ...
  </dependencies>
  ...
</project>

如果你希望使用 Hibernate Search(面向 Hibernate 应用程序的全文搜索),请从 Hibernate 网站下载它,并在你的类路径中添加 hibernate-search.jar 及其依赖项。或者,在你的 pom.xml 中添加以下依赖项。


<project>
  ...
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-search</artifactId>
      <version>${hibernate-search-version}</version>
    </dependency>
    ...
  </dependencies>
  ...
</project>

应用程序服务器和独立应用程序中实体管理器的配置驻留在持久性归档中。持久性归档是一个 JAR 文件,该文件必须定义驻留在 META-INF 文件夹中的一个 persistence.xml 文件。归档中包含的所有正确注释的类(即具有 @Entity 注释),所有带注释的包以及归档中包含的所有 Hibernate hbm.xml 文件都将被添加到持久性单元配置中,因此默认情况下,你的 persistence.xml 将极简


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
   <persistence-unit name="sample">
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>

下面是一个更完整的 persistence.xml 文件示例


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
   <persistence-unit name="manager1" transaction-type="JTA">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <mapping-file>ormap.xml</mapping-file>
      <jar-file>MyApp.jar</jar-file>
      <class>org.acme.Employee</class>
      <class>org.acme.Person</class>
      <class>org.acme.Address</class>
      <shared-cache-mode>ENABLE_SELECTOVE</shared-cache-mode>
      <validation-mode>CALLBACK</validation-mode>
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>
名称

(属性)每个实体管理器必须有一个名称。

事务类型

(属性)使用的事务类型。JTA 或 RESOURCE_LOCAL (在 JavaEE 环境中默认值为 JTA,在 JavaSE 环境中默认值为 RESOURCE_LOCAL)。如果使用了 jta-datasource,默认值为 JTA;如果使用了非 jta-datasource,则使用 RESOURCE_LOCAL。

提供程序

提供程序是 EJB 持久性提供程序的完全限定类名。如果你不使用多个 EJB3,则不必定义它。当你使用 EJB 持久性多个供应商实现时,这是必需的。

jta-data-source, non-jta-data-source

这是 javax.sql.DataSource 所在的 JNDI 名称。在没有 JNDI 可用 Datasource 的情况下运行时,必须使用 Hibernate 特定属性指定 JDBC 连接(见下文)。

映射文件

类元素指定你要映射的符合 EJB3 的 XML 映射文件。该文件必须在类路径中。根据 EJB3 规范,Hibernate EntityManager 将尝试加载位于 META_INF/orm.xml jar 文件中的映射文件。当然,任何显式映射文件都将被加载。事实上,可以在映射文件元素中提供任何 XML 文件,例如 hbm 文件或 EJB3 部署描述符。

jar 文件

jar 文件元素指定要分析的 jar。所有正确注释的类、注释的包以及此 jar 文件中的所有 hbm.xml 文件都将添加到持久性单元配置中。此元素主要用于 Java EE 环境。将其用于 Java SE 应被视为不可移植,在这种情况下需要一个绝对 URL。你还可以指向一个目录(当在测试环境中,persistence.xml 文件不在与你的域模型相同的根目录或 jar 中时,这特别有用)。


        <jar-file>file:/home/turin/work/local/lab8/build/classes</jar-file>
exclude-unlisted-classes

不要对主 jar 文件进行注释类检查。只有显式类将成为持久性单元的一部分。

类元素指定你要映射的完全限定类名。默认情况下,在存档中找到的所有正确注释的类和所有 hbm.xml 文件都将添加到持久性单元配置中。不过,你可以通过类元素添加一些外部实体。作为规范的扩展,你可以在 <class> 元素中添加一个包名称(例如 <class>org.hibernate.eg</class>)。注意,包将包括在包级别定义的元数据(即 package-info.java 中),它不会包括给定包的所有类。

共享缓存模式

默认情况下,如果使用 @Cacheable 进行注释,则实体将被选为二级缓存。但是你可以

有关详细信息,请参阅 Hibernate Annotation 文档。

validation-mode

默认情况下,将激活 Bean Validation(和 Hibernate Validator)。在向数据库发送实体时,在创建、更新(以及可选地删除)实体之前对实体进行验证。Hibernate 生成的数据库模式还反映了实体上声明的约束条件。

如需调整,可以

不幸的是,DDL 并非标准模式(但非常有用),并且你无法将其放入 <validation-mode> 中。要使用它,请添加常规属性


<property name="javax.persistence.validation.mode">
  ddl
</property>

利用此方式,可以混合 ddl 和 callback 模式


<property name="javax.persistence.validation.mode">
  ddl, callback
</property>
properties

properties 元素用于指定特定于供应商的属性。这是定义特定于 Hibernate 的配置的地方。还需要在此指定 JDBC 连接信息。

以下列出 JPA 2 标准属性。请务必参阅 Hibernate Core 的文档,以了解特定于 Hibernate 的属性。

以下属性只能在没有数据源/JNDI 的 SE 环境中使用

务必在 persistence 元素中定义语法定义,因为 JPA 规范要求进行模式验证。如果 systemIdpersistence_2_0.xsd 结束,Hibernate entityManager 将使用 hibernate-entitymanager.jar 中嵌入的版本。它不会从互联网获取资源。


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">

JPA 规范定义了一个引导程序,用于访问 EntityManagerFactoryEntityManager。引导类是 javax.persistence.Persistence,例如

EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1");


//or
Map<String, Object> configOverrides = new HashMap<String, Object>();
configOverrides.put("hibernate.hbm2ddl.auto", "create-drop");
EntityManagerFactory programmaticEmf =
    Persistence.createEntityManagerFactory("manager1", configOverrides);

第一个版本相当于带有空映射的第二个版本。映射版本是一组覆盖,将优先于 persistence.xml 文件中定义的任何属性。在第 2.2.1 节“打包” 中定义的所有属性都可以传递给 createEntityManagerFactory 方法,还有几个额外的属性

  • javax.persistence.provider定义使用的提供程序类

  • javax.persistence.transactionType定义使用的交易类型(JTARESOURCE_LOCAL

  • javax.persistence.jtaDataSource定义 JNDI 中的 JTA 数据源名称

  • javax.persistence.nonJtaDataSource定义 JNDI 中的非 JTA 数据源名称

  • javax.persistence.lock.timeout毫秒为单位的悲观锁超时(IntegerString

  • javax.persistence.query.timeout毫秒为单位的查询超时(IntegerString

  • javax.persistence.sharedCache.mode对应于 第 2.2.1 节“打包” 中定义的 share-cache-mode 元素。

  • javax.persistence.validation.mode对应于 第 2.2.1 节“打包” 中定义的 validation-mode 元素。

当调用 Persistence.createEntityManagerFactory() 时,持久化实现将在类路径中搜索任何使用 ClassLoader.getResource("META-INF/persistence.xml") 方法的 META-INF/persistence.xml 文件。实际上,Persistence 类将查看类路径中所有可用的持久化提供程序,并询问其中每个提供程序是否负责创建实体管理器工厂 manager1。该提供程序将从这些资源列表中尝试查找一个实体管理器,它匹配您在命令行中指定与 persistence.xml 文件中指定名称(当然,提供程序 element 必须与当前持续性提供程序匹配)。如果没有找到具有正确名称的 persistence.xml,或者如果没有找到预期的持久化提供程序,将引发 PersistenceException

除了 Hibernate 系统级设置外,Hibernate 中的所有可用属性都可以设置为 persistence.xml 文件的 properties 元素,或作为您传递给 createEntityManagerFactory() 的映射的覆盖项。有关完整列表,请参阅 Hibernate 参考文档。然而,EJB3 提供程序中只有几个属性可用。

表 2.1 Hibernate 实体管理器特定属性

属性名称说明
hibernate.ejb.classcache.<classname>类的类缓存策略[逗号缓存区域] 默认情况下无缓存,默认区域缓存为完全限定的类名(例如。hibernate.ejb.classcache.com.acme.Cat 读写或 hibernate.ejb.classcache.com.acme.Cat 读写,MyRegion)。
hibernate.ejb.collectioncache.<collectionrole>类的集合缓存策略 [逗号缓存区域] 默认情况下无缓存,默认区域缓存为完全限定的类名角色(例如。hibernate.ejb.classcache.com.acme.Cat 读写或 hibernate.ejb.classcache.com.acme.Cat 读写,MyRegion)。
hibernate.ejb.cfgfile用于配置 Hibernate 的 XML 配置文件(例如 /hibernate.cfg.xml)。
hibernate.archive.autodetection确定在解析 .par 归档时哪些元素由 Hibernate 实体管理器自动发现。(默认情况下 class,hbm)。
hibernate.ejb.interceptor一个可选的 Hibernate 拦截器。拦截器实例由所有 Session 实例共享。此拦截器必须实现 org.hibernate.Interceptor 并有一个无参数构造函数。此属性不能与 hibernate.ejb.interceptor.session_scoped 结合使用。
hibernate.ejb.interceptor.session_scoped一个可选的 Hibernate 拦截器。拦截器实例特定于给定的 Session 实例(因此可以是非线程安全的)。此拦截器必须实现 org.hibernate.Interceptor 并具有无参数构造函数。此属性不能与 hibernate.ejb.interceptor 结合使用。
hibernate.ejb.naming_strategy可选的命名策略。使用的默认命名策略是 EJB3NamingStrategy。您还可以考虑 DefaultComponentSafeNamingStrategy
hibernate.ejb.event.<eventtype>特定 eventtype 的事件侦听器列表。事件侦听器列表是由逗号分隔的全限定类名列表(例:hibernate.ejb.event.pre-load com.acme.SecurityListener, com.acme.AuditListener)。
hibernate.ejb.use_class_enhancer部署时是否使用应用服务器类增强(默认值为 false)
hibernate.ejb.discard_pc_on_close如果为 true,在调用该方法时持久化上下文将被丢弃(例如,clear())。否则,持久化上下文在事务完成之前仍将保持活动:所有对象都将保持管理,并且所有更改都将与数据库同步(默认值为 false,即等待事务完成)
hibernate.ejb.resource_scanner

默认情况下,Hibernate EntityManager 会扫描资源列表,以查找带注释的类和持久化部署描述符(例如 orm.xml 和 hbm.xml 文件)。

您可以通过实现 org.hibernate.ejb.packaging.Scanner 来自定义此扫描策略。此属性由容器实现者使用,以改善与 Hibernate 的集成。

接受 Scanner 的实例或实现 Scanner 的无参数构造函数类的文件名。


请注意,可以在同一配置中混合使用 XML <class> 声明和 hibernate.ejb.cfgfile。注意潜在的冲突。在 persistence.xml 中设置的属性将覆盖在定义的 hibernate.cfg.xml 中的属性。

注意

重要的是,不要覆盖 hibernate.transaction.factory_class,Hibernate EntityManager 会根据 EntityManager 类型自动设置适当的事务工厂(即 JTARESOURSE_LOCAL)。如果您在 Java EE 环境中工作,您可能希望设置 hibernate.transaction.manager_lookup_class

以下是在 Java SE 环境中的典型配置


<persistence>
   <persistence-unit name="manager1" transaction-type="RESOURCE_LOCAL">
      <class>org.hibernate.ejb.test.Cat</class>
      <class>org.hibernate.ejb.test.Distributor</class>
      <class>org.hibernate.ejb.test.Item</class>
      <properties>
         <property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver"/>
         <property name="javax.persistence.jdbc.user" value="sa"/>
         <property name="javax.persistence.jdbc.password" value=""/>
         <property name="javax.persistence.jdbc.url" value="jdbc:hsqldb:."/>
         <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/
         <property name="hibernate.max_fetch_depth" value="3"/>
       
         <!-- cache configuration -->
         <property name="hibernate.ejb.classcache.org.hibernate.ejb.test.Item" value="read-write"/>
         <property name="hibernate.ejb.collectioncache.org.hibernate.ejb.test.Item.distributors" value="read-write, RegionName"/>

         <!-- alternatively to <class> and <property> declarations, you can use a regular hibernate.cfg.xml file -->
         <!-- property name="hibernate.ejb.cfgfile" value="/org/hibernate/ejb/test/hibernate.cfg.xml"/ -->
      </properties>
   </persistence-unit>
</persistence>

为了简化编程配置,Hibernate Entity Manager 提供了一个专有的 API。此 API 与 Configuration API 非常相似,并共享相同概念:Ejb3Configuration。有关如何使用它的更详细信息,请参阅 JavaDoc 和 Hibernate 参考指南。

TODO:在 setDatasource() 等一些 API 上更具描述性

Ejb3Configuration cfg = new Ejb3Configuration();

EntityManagerFactory emf = 
  cfg.addProperties( properties ) //add some properties
     .setInterceptor( myInterceptorImpl ) // set an interceptor
     .addAnnotatedClass( MyAnnotatedClass.class ) //add a class to be mapped
     .addClass( NonAnnotatedClass.class ) //add an hbm.xml file using the Hibernate convention
     .addRerousce( "mypath/MyOtherCLass.hbm.xml ) //add an hbm.xml file
     .addRerousce( "mypath/orm.xml ) //add an EJB3 deployment descriptor
     .configure("/mypath/hibernate.cfg.xml") //add a regular hibernate.cfg.xml
     .buildEntityManagerFactory(); //Create the entity manager factory

Hibernate Entity Manager 需要增强 Hibernate 核心以实现所有 JPA 语义。它通过 Hibernate 的事件侦听器系统来实现这一点。在您自己使用事件系统时请小心,您可能会覆盖一些 JPA 语义。一种安全的方法是将您的事件侦听器添加到以下列表中。


请注意,如果安全性未启用,将删除 JACC*EventListeners

您可以通过属性(请参阅 配置和引导)或 ejb3configuration.getEventListeners() API 配置事件监听器。

如果你不知道所查找对象的标识符值,则需要一个查询。Hibernate EntityManager 实现支持易于使用且功能强大的面向对象查询语言(JP-QL),它受到 HQL(反之亦然)的启发。严格来说,HQL 是 JP-QL 的超集。这两种查询语言在数据库之间是可移植的,使用实体和属性名称作为标识符(而不是表和列名称)。你还可以使用数据库的本机 SQL 来表达查询,同时 JPA 可选择支持将结果集转换为 Java 业务对象。

JP-QL 和 SQL 查询由 javax.persistence.Query 的一个实例表示。该接口提供用于参数绑定、结果集处理和查询执行的方法。总是使用当前实体管理器创建查询

List<?> cats = em.createQuery(

    "select cat from Cat as cat where cat.birthdate < ?1")
    .setParameter(1, date, TemporalType.DATE)
    .getResultList();
List<?> mothers = em.createQuery(
    "select mother from Cat as cat join cat.mother as mother where cat.name = ?1")
    .setParameter(1, name)
    .getResultList();
List<?> kittens = em.createQuery(
    "from Cat as cat where cat.mother = ?1")
    .setEntity(1, pk)
    .getResultList();
Cat mother = (Cat) em.createQuery(
    "select cat.mother from Cat as cat where cat = ?1")
    .setParameter(1, izi)
    .getSingleResult();

通常通过调用 getResultList() 来执行查询。此方法将查询的结果实例完全加载到内存中。由查询检索的实体实例处于持久状态。如果您知道您的查询只会返回单个对象,getSingleResult() 方法提供了一个快捷方式。

JPA 2 为查询提供了更多类型安全的方法。真正的类型安全方法是在 第 9 章,查询条件 中说明的 Criteria API。

CriteriaQuery<Cat> criteria = builder.createQuery( Cat.class );

Root<Cat> cat = criteria.from( Cat.class );
criteria.select( cat );
criteria.where( builder.lt( cat.get( Cat_.birthdate ), catDate ) );
List<Cat> cats = em.createQuery( criteria ).getResultList(); //notice no downcasting is necessary

但即使在使用 JP-QL 时也能受益于一些类型安全便利性(请注意,它不像编译器那样类型安全,因为它必须信任您的返回类型。

//No downcasting since we pass the return type

List<Cat> cats = em.createQuery(
    "select cat from Cat as cat where cat.birthdate < ?1", Cat.class)
    .setParameter(1, date, TemporalType.DATE)
    .getResultList();

注意

我们强烈推荐 Criteria API 方法。虽然它更冗长,但它提供编译器强制的安全(包括属性名称),当应用程序将切换到维护模式时,它将会非常有益。

合并操作非常智能,可以自动检测是否必须以插入或更新为结果来合并分离实例。换句话说,您不必担心将新实例(而不是分离实例)传递给 merge(),实体管理器会为您弄清楚这一点

// In the first entity manager

Cat cat = firstEntityManager.find(Cat.class, catID);
// In a higher layer of the application, detached
Cat mate = new Cat();
cat.setMate(mate);
// Later, in a new entity manager
secondEntityManager.merge(cat);   // update existing state
secondEntityManager.merge(mate);  // save the new instance

对于新用户而言,merge() 的用法和语义似乎令人困惑。首先,只要您不尝试在另一个新实体管理器中使用在一个实体管理器中加载的对象状态,就根本不需要使用 merge()。一些整个应用程序永远不会使用此方法。

通常在以下场景中使用 merge()

以下是 merge() 的确切语言

实体管理器会不时执行所需的 SQL DML 语句,以同步数据存储与内存中保存的对象状态。该过程称为刷新。

刷新会自动在以下时间点发生(这是 Hibernate 特有的,并且不在规范中定义)

(*) 如果事务处于活动状态

SQL 语句按以下顺序发出

(异常:使用由应用程序分配的标识符的实体实例在保存时插入。)

只有在您显式地 flush() 时才保证实体管理器执行 JDBC 调用,但只能保证执行顺序。但是,Hibernate 保证 Query.getResultList()/Query.getSingleResult() 永远不会返回陈旧的数据;如果在活动事务中执行,它们也不会返回错误的数据。

可以通过更改默认行为来减少刷新频率。实体管理器的 FlushModeType 定义两种不同的模式:只在提交时刷新或除非显式地调用 flush(),否则自动使用说明的例程刷新。

em = emf.createEntityManager();

Transaction tx = em.getTransaction().begin();
em.setFlushMode(FlushModeType.COMMIT); // allow queries to return stale state
Cat izi = em.find(Cat.class, id);
izi.setName(iznizi);
// might return stale data
em.createQuery("from Cat as cat left outer join cat.kittens kitten").getResultList();
// change to izi is not flushed!
...
em.getTransaction().commit(); // flush occurs

在刷新期间,可能会发生异常(例如,如果 DML 操作违反了约束)。TODO:添加指向异常处理的链接。

Hibernate 提供的刷新模式多于 JPA 规范中描述的刷新模式。特别是,针对长时间通信的 FlushMode.MANUAL。有关详细信息,请参阅 Hibernate 核心参考文档。

保存、删除或重新附加各个对象非常繁琐,尤其是当您处理相关对象的图形时。亲子关系是一种常见情况。考虑以下示例

如果父子关系中的子级为值类型(例如,地址或字符串的集合),那么它们的生存期将取决于父级,并且不再需要采取进一步的措施来方便地“级联”状态更改。当保存父级时,值类型子级对象也将被保存,当删除父级时,子级将被删除,依此类推。这甚至适用于某些操作,例如从集合中删除子级;Hibernate 将检测到这一点,并且由于值类型对象不能具有共享引用,因此会从数据库中删除子级。

现在考虑父对象和子对象为实体(例如类别和项目,或父猫和子猫)而值类型的情况。实体有自己的生命周期,支持共享引用(因此从集合中删除实体并不意味着它可以被删除),并且默认情况下,不会级联将状态从一个实体传给任何其他关联实体。EJB3 规范不要求通过可达性进行持久化。它支持一个更灵活的瞬态持久化模型,就像最早出现在 Hibernate 中的那样。

对于实体管理器的每个基本操作 - 包括 persist()merge()remove()refresh() - 都有一个相应的级联样式。级联样式分别命名为 PERSISTMERGEREMOVEREFRESHDETACH。如果您希望级联操作到关联实体(或实体集合),则必须在关联注释中注明这一点

@OneToOne(cascade=CascadeType.PERSIST)

级联选项可以组合

@OneToOne(cascade= { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH } )

您甚至可以使用 CascadeType.ALL 指定应为特定关联级联所有操作。请记住,默认情况下,不级联任何操作。

还有一个附加的级联模式用于描述孤儿删除(即不再链接到拥有对象的,对象应由 Hibernate 自动删除。在 @OneToOne@OneToMany 中使用 orphanRemoval=true。有关更多信息,请查看 Hibernate Annotations 文档。

Hibernate 提供更多原生级联选项,请查阅 Hibernate Annotations 手册和 Hibernate 参考指南以获取更多信息。

建议

当二级缓存被激活时(请参见 第 2.2.1 节“打包”和 Hibernate Annotations 参考文档),Hibernate 也会确保使用并正确更新二级缓存。但是,可以通过传递两个属性来调整这些设置

  • javax.persistence.cache.retrieveMode,它接受 CacheRetrieveMode

  • javax.persistence.cache.storeMode,它接受 CacheStoreMode

CacheRetrieveMode 控制 Hibernate 如何从二级缓存访问信息: USE(这是默认值)或 BYPASS(这意味着忽略缓存)。 CacheStoreMode 控制 Hibernate 如何将信息推送到二级缓存: USE(这是默认值),在从数据库中读取和写入数据库时将数据推送到缓存中,BYPASS(不插入新的数据到缓存中,但可以使过时数据无效)和 REFRESH(与默认值类似,但也强制将数据推送到缓存,在数据库读取时执行此操作,即使数据已缓存在缓存中也是如此)。

可以在以下位置设置这些属性

  • 通过 setProperty 方法,在一个特定的 EntityManager

  • 通过查询提示(setHint 方法),在一个查询上

  • 在调用 find()refresh() 并在适当的 Map 中传递属性时

JPA 还引入了一个用于查询二级缓存并手动清除数据的信息。

Cache cache = entityManagerFactory.getCache();


if ( cache.contains(User.class, userId) ) {
   //load it as we don't hit the DB
}
cache.evict(User.class, userId); //manually evict user form the second-level cache
cache.evict(User.class); //evict all users from the second-level cache
cache.evictAll(); //purge the second-level cache entirely

注意

元模型本身在 [JPA 2 规范] 的第 5 章 元模型 API 中进行描述。[JPA 2 规范] 的第 6 章 准则 API 描述并展示在准则查询中对元模型的使用,正如 第 9 章,准则查询 一样。

元模型是描述您的域模型的一组对象。 javax.persistence.metamodel.Metamodel 充当这些元模型对象的储存库并提供对它们的访问权限,可以通过 javax.persistence.EntityManagerFactoryjavax.persistence.EntityManager 及其 getMetamodel 方法获取。

此元模型非常重要,共有 2 个方面。首先,它允许提供程序和框架以通用方式处理应用程序的域模型。持久性提供程序已经拥有某种形式的元模型,他们使用该元模型来描述被映射的域模型。然而,此 API 为对现有信息定义了单一的、独立的访问。例如,验证框架可使用此信息来理解关联;封送框架或许会使用此信息来决定封送实体关系图的多少。此用法超出了本说明文档的范围。

重要提示

截至今日,JPA 2 元模型不提供任何用于访问与物理模型相关的关系信息的功能。预计此问题将在规范的未来版本中得到解决。

其次,从应用程序编写者的角度,它允许非常流畅地表达完全类型安全的条件查询,尤其是静态元模型方法。[JPA 2 规范]定义了一些元模型的访问和使用方式,其中包括我们将稍后阐述的静态元模型方法。如果代码具有领域模型的先验知识,则静态元模型方法非常棒。第 9 章,条件查询在其示例中独家使用这种方法。

静态元模型是一系列类,它们“镜像”领域模型中的实体和嵌入对象,并且提供对镜像类属性的元数据的静态访问。我们只讨论 [JPA 2 规范] 称之为 规范元模型 的内容

 

  • 对于包 p 中的每个受管类 X,将创建包 p 中的元模型类 X_

  • 元模型类的名称是从受管类的名称派生而来,方法是在受管类的名称后面追加“_”。

  • 元模型类 X_ 必须使用 javax.persistence.StaticMetamodel 注释进行注释[1]

  • 如果类 X 扩展另一个类 S,其中 SX 扩展的最派生受管类(即实体或映射超类),则类 X_ 必须扩展类 S_,其中 S_ 是为 S 创建的元模型类。

  • 对于类 X 声明的每个持久性非集合值属性 y,其中 y 的类型为 Y,则元模型类必须包含以下声明

    public static volatile SingularAttribute<X, Y> y;

  • 对于类 X 声明的每个持久性集合值属性 z,其中 z 的元素类型为 Z,则元模型类必须包含以下声明

    • 如果z的集合类型是java.util.Collection,则

      public static volatile CollectionAttribute<X, Z> z;

    • 如果集合类型为 z java.util.Set,那么

      public static volatile SetAttribute<X, Z> z;

    • 如果集合类型为 z java.util.List,那么

      public static volatile ListAttribute<X, Z> z;

    • 如果集合类型为 z java.util.Map,那么

      public static volatile MapAttribute<X, K, Z> z;

      其中 KX 类中映射键的类型

要导入必要的 javax.persistence.metamodel 类型,必须包括导入声明(例如,javax.persistence.metamodel.SingularAttributejavax.persistence.metamodel.CollectionAttributejavax.persistence.metamodel.SetAttributejavax.persistence.metamodel.ListAttributejavax.persistence.metamodel.MapAttribute)和所有类 XYZK

 
 -- [JPA 2 规范,第 6.2.1.1 节,第 198-199 页]

注意

如果愿意,这些规范化元模型类可以手动生成,但预计大多数开发人员更愿意使用注释处理器。注释处理器本身不在本文档的讨论范围之内。但是,Hibernate 团队确实开发了一个注释处理器工具来生成规范化元模型。请参见Hibernate 元模型生成器

构建 Hibernate EntityManagerFactory 时,它将为所知道的每个受管理类型查找规范化元模型类,如果找到任何规范化元模型类,它将向其中注入适当的元模型信息,如 [JPA 2 规范,第 6.2.2 节,第 200 页] 所述



[1] (摘自原作) 如果该类是生成的,则应使用 javax.annotation.Generated 注释对该类进行注释。对静态元模型类使用其他任何注释都是未定义的。

有关 Hibernate 实体管理器和并发控制最重要的要点在于它非常易于理解。Hibernate 实体管理器直接使用 JDBC 连接和 JTA 资源,而不会添加任何附加的锁定行为。我们强烈建议您花一些时间了解 JDBC、ANSI 和数据库管理系统的隔离规范。Hibernate 实体管理器只添加自动版本控制,但不锁定内存中的对象或更改数据库事务的隔离级别。基本上,您使用 Hibernate 实体管理器的方式与使用数据库资源的直接 JDBC(或 JTA/CMT)的方式相同。

我们从EntityManagerFactoryEntityManager以及数据库事务和长期工作单元的粒度开始讨论 Hibernate 中的并发控制。

在本章中,除非明确表示,我们将会混合匹配实体管理器和持久性上下文的概念。一个是 API 和编程对象,另一个是范围定义。不过,请记住它们之间的本质区别。持久性上下文通常在 Java EE 中绑定到 JTA 事务,且持久性上下文在事务边界时开始和结束(事务范围),除非您使用扩展实体管理器。有关更多信息,请参阅第 1.2.3 节“持久性上下文范围”

EntityManagerFactory是一个创建成本高昂、线程安全的对象,旨在供应用程序中所有线程共享。它在应用程序启动时通常只创建一次。

EntityManager是一个创建成本低廉、非线程安全的对象,应该仅用于一次业务流程、一个工作单元,然后丢弃。只有在有需要的情况下,EntityManager才会获取 JDBC Connection(或Datasource),因此即便您不确定某个特定请求是否需要数据访问,也可以放心打开和关闭EntityManager。(如果您使用请求拦截来实施以下某些模式,这样做很重要。)

要完成此概念,您还必须考虑数据库事务。数据库事务必须尽量简短,以减少数据库中的锁定争用。长时间的数据库事务将阻止您的应用程序扩展到高并发负载。

工作单元的范围是什么?一个 Hibernate EntityManager可以跨越多个数据库事务,还是它们之间是一对一的关系?您什么时候应该打开和关闭Session,以及如何划分数据库事务边界?

首先,请勿使用实体管理器单一操作反模式,即不要在单线程的每个简单数据库调用中打开和关闭EntityManager!当然,数据库事务亦是如此。应用程序中的数据库调用使用经过计划的顺序,这些顺序被分组为原子的工作单元。(请注意,这意味着在每个 SQL 语句后自动提交在应用程序中是无用的,此模式旨在用于临时的 SQL 控制台工作。)

多用户客户端/服务器应用程序中最常见的模式是实体管理器单一请求。在此模型中,将一个请求从客户端发送至服务器(JPA 持久性层运行于此),打开一个新的EntityManager,并在该工作单元中执行所有数据库操作。一旦工作完成(且客户端的响应已准备好),就会刷新和关闭持久性上下文以及实体管理器对象。您还将使用单个数据库事务来服务于客户端请求。二者之间的关系是一对一,且此模型完美适合许多应用程序。

这是 Java EE 环境中的默认 JPA 持久性模型(JTA 受限,事务作用域持久性上下文);注入(或查找)的实体管理器为特定 JTA 事务共享相同持久性上下文。JPA 的优点在于您不必再关心这些内容,只通过实体管理器查看数据访问,并将会话 bean 上事务作用域的划分视为完全正交。

挑战在于在 EJB3 容器之外实现此行为(和其他行为):不仅 EntityManager 和资源本地事务必须正确启动和结束,而且还必须能够用于数据访问操作。理想情况下,使用拦截器实现工作单元的界定,该拦截器在请求到达非 EJB3 容器服务器时运行,并在发送响应之前(即,如果您使用的是独立服务器容器,则为 ServletFilter)运行。我们建议将 EntityManager 绑定到用于处理请求的线程,并使用 ThreadLocal 变量。这样便可在此线程中运行的所有代码中轻松访问(就像访问静态变量一样)。根据您选择的数据库事务界定机制,您还可以在 ThreadLocal 变量中保存事务上下文。社区内为此实现模式的模式被称为 线程本地会话查看中的打开会话。您可轻松扩展 Hibernate 参考文档中所示的 HibernateUtil 来实现此模式,不必借助任何外部软件(事实上,非常普通)。当然,您必须找到一种方法来实现拦截器,并在环境中进行设置。请参阅 Hibernate 网站获取提示和示例。再次提示,您的首选当然是 EJB3 容器 - 最好是像 JBoss 应用程序服务器这样轻量且模块化的容器。

基于请求的实体管理器的模式并不是可用于设计工作单元的唯一有用概念。许多业务流程都需要与用户交互的完整系列与数据库访问交错。在 Web 和企业应用程序中,数据库事务跨越用户交互(在请求之间可能会有很长的等待时间)是不可接受的。请考虑以下示例

从用户的角度来看,我们称此工作单元为长期运行 应用程序事务。有很多方法可用于在应用程序中实现此事务。

第一个简单的实现可能会在用户思考时间内保持EntityManager和数据库事务打开,同时在数据库中锁定,以防止并发修改并保证隔离性和原子性。当然,这是一个反模式,一种悲观的方法,因为锁争用将不允许应用程序随着并发用户数量而扩展。

显然,我们必须使用多个数据库事务来实现应用程序事务。在这种情况下,管理业务流程的隔离性成为应用程序层的局部责任。单个应用程序事务通常跨越多个数据库事务。仅当其中一个数据库事务(最后一个)存储更新后的数据时,它将是原子的,而所有其他事务仅读取数据(例如,跨越多个请求/响应周期的向导式对话框)。这比听起来容易实现,尤其是在使用 JPA 实体管理器和持久性上下文功能时

每个请求一个实体管理器带分离对象每个应用程序事务一个实体管理器都有优点和缺点,我们将在本章后面在乐观并发控制的背景下对此进行讨论。

待办事项:此注释可能应该稍后出现。

应用程序可能会在两个不同的持久性上下文中同时访问相同的持久化状态。但是,受管理类的实例永远不会在两个持久性上下文之间共享。因此,有两个不同的标识概念

然后,对于附加到一个 特定的 持续性上下文的对象(即,在 EntityManager 范围内),这两个概念是等同的,而 JVM 一致性对于数据库一致性是由 Hibernate Entity Manager 保证的。然而,当应用程序可能在两个不同的持续性上下文中并行访问“相同”的(持续性一致性)业务对象时,这两个实例实际上将是“不同的”(JVM 一致性)。冲突会在刷新/提交时使用乐观的方法,使用(自动版本控制)解决。

这种方法让 Hibernate 和数据库应对并行性;它还提供了最佳的可伸缩性,因为保证在单线程工作单元中的一致性,只需要经济型锁定或其他同步方法。应用程序永远不需要同步任何业务对象,只要它坚持对每个 EntityManager 使用单一线程即可。在持续性上下文中,应用程序可以安全地使用 == 来比较实体。

然而,在持续性上下文外使用 == 的应用程序可能会看到意外的结果。这种情况甚至可能出现在一些意外的地方,例如,如果你将两个分离实例放入同一个 Set 中。两者都可能具有相同的数据库一致性(即,它们代表相同行),但 JVM 一致性按惯例不适用于分离状态的实例。开发人员必须覆盖持续性类中的 equals()hashCode() 方法,并实现他自己的对象相等概念。有一个警告:切勿使用数据库标识符来实现相等性,请使用业务密钥,这是唯一、通常不可变属性的组合。如果瞬态实体变成持续性实体,数据库标识符将更改(请参阅 persist() 操作的契约)。如果瞬态实例(通常连同分离的实例)保存在 Set 中,更改哈希码会破坏 Set 的契约。良好的业务密钥的属性不必像数据库主键一样稳定,你只需保证在对象在同一个 Set 中时稳定即可。请访问 Hibernate 网站,全面了解此问题。还要注意,这不是 Hibernate 问题,而是只简单地实现了 Java 对象一致性和相等性。

切勿使用反模式 entitymanager-per-user-sessionentitymanager-per-application(当然,此规则有罕见例外,例如 entitymanager-per-application 可能在桌面应用程序中可接受的,使用手动刷新持续性上下文)。请注意,以下一些问题也可能出现在推荐模式中,请确保在制定设计决策之前了解含义。

数据库(或系统)事务边界始终是必需的。没有数据库事务,数据库通信就无法进行(这似乎让习惯了自动提交模式的许多开发人员感到迷惑)。即使是只读操作,也始终使用明确的事务边界。根据您的隔离级别和数据库功能,这可能不是必需的,但如果始终明确划定事务,则不会有什么弊端。不过,当您需要在 EXTENDED 持久性上下文中保留修改时,您将不得不在事务外部执行操作。

JPA 应用程序可以在非托管(即独立、简单的 Web 或 Swing 应用程序)和托管 Java EE 环境中运行。在非托管环境中,EntityManagerFactory 通常负责其自己的数据库连接池。应用程序开发人员必须手动设置事务边界,换句话说,自行启动、提交或回滚数据库事务。托管环境通常提供容器托管的事务,例如,通过 EJB 会话 bean 的注释以声明性方式定义事务程序集。然后,不再需要以编程方式划分事务,甚至可以自动刷新EntityManager

通常,结束一个工作单元涉及四个不同的阶段

我们现在将仔细研究托管和非托管环境中的事务划分和异常处理。

如果 JPA 持久性层在非托管环境中运行,则数据库连接通常会在后台由 Hibernate 的池机制处理。常见的实体管理器和事务处理习惯用语如下

// Non-managed environment idiom

EntityManager em = emf.createEntityManager();
EntityTransaction tx = null;
try {
    tx = em.getTransaction();
    tx.begin();
    // do some work
    ...
    tx.commit();
}
catch (RuntimeException e) {
    if ( tx != null && tx.isActive() ) tx.rollback();
    throw e; // or display error message
}
finally {
    em.close();
}

你不必flush()显式地EntityManager - 调用commit()会自动触发同步。

close() 的调用标记 EntityManager 的结束。 close() 的主要含义是释放资源 - 确保始终在保证的 finally 块内关闭,永远不要在保证的 finally 块外关闭。

你很可能永远不会在普通应用程序的业务代码中看到这个习惯用语,应始终在“顶部”捕获致命(系统)异常。换句话说,执行实体管理器调用(在持久性层)的代码和处理RuntimeException(且通常只能清理和退出)的代码位于不同的层。自己设计这方面可能是一个挑战,应在任何时候使用 J2EE/EJB 容器服务。将在本章后面讨论异常处理。

如果您的持久化层在应用程序服务器中运行(例如,位于 EJB3 会话 bean 之后),则实体管理器在内部获取的每个数据源连接都将自动成为全局 JTA 事务的一部分。Hibernate 为这种集成提供两种策略。

如果您使用 bean 管理的事务 (BMT),则该代码将如下所示

// BMT idiom

@Resource public UserTransaction utx;
@Resource public EntityManagerFactory factory;
public void doBusiness() {
    EntityManager em = factory.createEntityManager();
    try {
    // do some work
    ...
    utx.commit();
}
catch (RuntimeException e) {
    if (utx != null) utx.rollback();
    throw e; // or display error message
}
finally {
    em.close();
}

在 EJB3 容器中使用容器托管的事务 (CMT) 时,事务界定是在会话 bean 注解或部署描述符中完成,而非以编程方式完成。一旦事务完成,EntityManager 将自动刷新(而且如果您已注入或查找 EntityManager,它也将自动关闭)。如果您未捕获到该异常,则在 EntityManager 使用期间如果发生异常,事务将自动回滚。由于 EntityManager 异常是 RuntimeException,它们会根据 EJB 规范回滚事务(系统异常与应用程序异常)。

务必让 Hibernate EntityManager 定义 hibernate.transaction.factory_class(即不覆盖此值)。请记住还要设置 org.hibernate.transaction.manager_lookup_class

如果您在 CMT 环境中工作,您可能还会想要在您代码的不同部分中使用相同的实体管理器。通常,在非托管环境中,您将使用 ThreadLocal 变量持有该实体管理器,但单个 EJB 请求可能在不同的线程中执行(例如,会话 bean 调用其他会话 bean)。EJB3 容器将为您负责持久化上下文传播。EJB3 容器将通过注入或查找返回一个实体管理器,如果存在,则此管理器将具有与 JTA 上下文绑定的相同持久化上下文,或者创建新管理器并将其绑定(请参阅 第 1.2.4 节,“持久化上下文传播” 。)

我们针对 CMT 和 EJB3 容器使用的实体管理器/事务管理习语被简化为以下内容

//CMT idiom through injection

@PersistenceContext(name="sample") EntityManager em;

如果您使用 Java Context 和依赖项注入 (CDI),则采用以下内容。

@Inject EntityManager em;

换言之,在托管环境中,您只需注入 EntityManager、执行数据访问工作并使容器处理其余事务即可。事务边界在会话 Bean 的注释或部署描述符中进行声明性设置。实体管理器和持久性上下文的生命周期完全由容器管理。

由于 JTA 规范的一项不恰当限制,Hibernate 无法自动清除 scroll()iterate() 返回的任何未关闭的 ScrollableResultsIterator 实例。您必须通过从 finally 块显式调用 ScrollableResults.close()Hibernate.close(Iterator) 来释放基础数据库游标。(当然,大多数应用程序都可以轻松避免在 CMT 代码中使用 scroll()iterate()。)

如果 EntityManager 抛出异常(包括任何 SQLException),则您应立即回滚数据库事务,调用 EntityManager.close()(如果已调用 createEntityManager())并放弃 EntityManager 实例。某些 EntityManager 方法不会将持久性上下文保留在一致状态。实体管理器抛出的任何异常都无法视为可恢复异常。请通过在 finally 块中调用 close() 确保会关闭 EntityManager。请注意,容器托管实体管理器会为您执行该操作。您只需让 RuntimeException 传播到容器即可。

Hibernate 实体管理器通常会引发一些异常,其中封装了 Hibernate 核心异常。由 EntityManager API 引发的常见异常包括

HibernateException 是一种未经检查的异常,它包装了 Hibernate 持久化层中可能发生的诸多错误。请注意,Hibernate 还可以抛出其他未经检查的异常,这些异常不是 HibernateException。同样,这些异常不可恢复,应采取适当的措施。

在与数据库交互时抛出的 SQLException 由 Hibernate 包装成 JDBCException。事实上,Hibernate 会尝试将异常转换为 JDBCException 的更有意义的子类。基础 SQLException 始终可通过 JDBCException.getCause() 获得。Hibernate 使用附加到 SessionFactorySQLExceptionConverter,将 SQLException 转换为适当的 JDBCException 子类。默认情况下,SQLExceptionConverter 由配置的方言定义;不过,也可以插入自定义实现(请参阅 SQLExceptionConverterFactory 类的 javadoc 了解详情)。标准 JDBCException 子类型有:

以这种方式定义的所有应用程序管理实体管理器和容器管理持久化上下文都是 EXTENDED。这意味着持久化上下文类型超出事务生命周期。我们应该了解在事务范围之外进行的操作会有什么后果。

EXTENDED 持久上下文中,实体管理器的所有只读操作可以在事务外部执行(find()getReference()refresh()detach() 和读查询)。某些修改操作也可以在事务外部执行,但必须排队等待持久上下文加入事务:persist()merge()remove()。某些操作无法在事务外部调用:flush()lock(),以及更新/删除查询。

惟一一种与高并发和高可扩展性相符的方法是带有版本控制的乐观并发控制。版本检查使用版本号或时间戳检测冲突更新(防止丢失更新)。Hibernate 为编写使用乐观并发应用程序代码提供了三种可能的方法。我们展示的使用案例处于长期应用程序事务的上下文中,但是版本检查还具有在单个数据库事务中防止丢失更新的好处。

在没有持久机制太多帮助的情况下实现时,与数据库的每次交互发生在一个新的 EntityManager 中,开发人员负责在操作所有持久实例之前从数据库重新加载它们。这种方法强制应用程序执行自己的版本检查来确保应用程序事务隔离。这种方法在数据库访问方面效率最低。该方法与 EJB2 实体最相似。

// foo is an instance loaded by a previous entity manager

em = factory.createEntityManager();
EntityTransaction t = em.getTransaction();
t.begin();
int oldVersion = foo.getVersion();
Foo dbFoo = em.find( foo.getClass(), foo.getKey() ); // load the current state
if ( dbFoo.getVersion()!=foo.getVersion ) throw new StaleObjectStateException();
dbFoo.setProperty("bar");
t.commit();
em.close();

使用 @Version 映射 version 属性,如果实体已修改,实体管理器在刷新期间会自动对其增量。

当然,如果您在低数据并发环境中操作并且不需要版本检查,则可以使用此方法并跳过版本检查。在这种情况下,上次提交获胜 将成为您长期应用程序事务的默认策略。请记住,这可能会混淆应用程序的用户,因为他们可能会遇到丢失更新的情况,而没有错误信息或合并冲突更改的机会。

显然,仅在极琐碎的情况下手动版本检查才可行,对于大多数应用程序来说不切实际。通常,不仅要检查单个实例,而且还要检查已修改对象的完整图。Hibernate 提供了自动版本检查,其中分离实例或扩展的实体管理器和持久性上下文作为设计范例。

整个应用程序事务使用单个持久性上下文。实体管理器在刷新时间检查实例版本,如果检测到并发修改,则抛出一个异常。由开发人员捕获并处理此异常(常见选项是用户有机会合并其更改或使用非陈旧数据重新启动业务流程)。

EXTENDED 持久性上下文中,在活动事务之外进行的所有操作都会排队。在活动事务中(最坏情况下在提交时间)执行时将刷新 EXTENDED 持久性上下文。

当等待用户交互时,Entity Manager 与任何底层 JDBC 连接断开连接。在应用程序管理的扩展实体管理器中,这将在事务完成时自动发生。在持有容器管理的扩展实体管理器的有状态会话 bean 中(即使用 @PersistenceContext(EXTENDED) 进行标注的 SFSB),这也会透明地发生。这种方式在数据库访问方面最有效。应用程序不必处理版本检查或合并分离实例,也不必在每个数据库事务中重新加载实例。对于那些可能因为打开和关闭的连接数量而担忧的人,请记住连接提供程序应是连接池,因此没有性能影响。以下示例显示了非托管环境中的惯用语

// foo is an instance loaded earlier by the extended entity manager

em.getTransaction.begin(); // new connection to data store is obtained and tx started
foo.setProperty("bar");
em.getTransaction().commit();  // End tx, flush and check version, disconnect

foo 对象仍然知道其加载的 持久性上下文。通过 getTransaction.begin();,实体管理器获取新的连接并继续持久性上下文。方法 getTransaction().commit() 不仅会刷新和检查版本,还会断开实体管理器与 JDBC 连接,并将连接返回到池。

如果持久性上下文太大,无法在用户思考时间内存储,并且不知道将其存储在何处,则此模式可能有缺陷。例如,应尽可能HttpSession的大小。由于持久性上下文也是(强制性的)一级缓存,并且包含所有加载的对象,因此我们可能只能对一些请求/响应循环使用此策略。事实上,这样做是推荐的,因为持久性上下文很快也会包含陈旧数据。

您决定在请求期间将扩展实体管理器存储在哪,在 EJB3 容器中,只需使用如上所述的有状态会话 bean。不要将其传输到 Web 层(或将其序列化到单独层),以将其存储在 HttpSession 中。在非托管双层环境中,HttpSession 实际上可能是存储它的正确位置。

应用程序通常需要针对持久性机制中发生的特定事件作出反应。这允许实现特定类型的通用功能,并扩展内置功能。JPA 规范为此提供了两种相关机制。

可以将实体的方法指定为回调方法,以接收特定实体生命周期事件的通知。回调方法由回调注解注释。您还可以定义实体侦听器类,以代替直接在实体类内定义的回调方法。实体侦听器是一个无状态类,带有无参数构造函数。通过使用 @EntityListeners 注解对实体类进行注释来定义实体侦听器

@Entity

@EntityListeners(class=Audit.class)
public class Cat {
    @Id private Integer id;
    private String name;
    private Calendar dateOfBirth;
    @Transient private int age;
    private Date lastUpdate;
    //getters and setters
    /**
     * Set my transient property at load time based on a calculation,
     * note that a native Hibernate formula mapping is better for this purpose.
     */
    @PostLoad
    public void calculateAge() {
        Calendar birth = new GregorianCalendar();
        birth.setTime(dateOfBirth);
        Calendar now = new GregorianCalendar();
        now.setTime( new Date() );
        int adjust = 0;
        if ( now.get(Calendar.DAY_OF_YEAR) - birth.get(Calendar.DAY_OF_YEAR) < 0) {
            adjust = -1;
        }
        age = now.get(Calendar.YEAR) - birth.get(Calendar.YEAR) + adjust;
    }
}
public class LastUpdateListener {
    /**
     * automatic property set before any database persistence
     */
    @PreUpdate
    @PrePersist
    public void setLastUpdate(Cat o) {
        o.setLastUpdate( new Date() );
    }
}

同一个回调方法或实体侦听器方法可以用多个回调注解注释。对于给定的实体,您不能有两个方法由同一个回调注解注释,无论它是回调方法还是实体侦听器方法。回调方法是一个无参数方法,没有返回类型,任何任意名称都可以。实体侦听器的签名是 void <METHOD>(Object),其中 Object 是实际的实体类型(请注意,Hibernate 实体管理器放宽了此约束,允许 Object of java.lang.Object 类型(允许共享跨多个实体的侦听器。)

回调方法可以引发 RuntimeException。如果存在当前事务,则必须回滚。定义了以下回调


回调方法不得调用 EntityManagerQuery 方法!

JPA 规范允许通过 JPA 部署描述符覆盖注释。还有一个额外的功能可能是有效的:默认事件侦听器。


<?xml version="1.0" encoding="UTF-8"?>

<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd"
                 version="2.0"
        >
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener class="org.hibernate.ejb.test.pack.defaultpar.IncrementListener">
                    <pre-persist method-name="increment"/>
                </entity-listener>
            </entity-listeners>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
    <package>org.hibernate.ejb.test.pack.defaultpar</package>
    <entity class="ApplicationServer">
        <entity-listeners>
            <entity-listener class="OtherIncrementListener">
                <pre-persist method-name="increment"/>
            </entity-listener>
        </entity-listeners>


        <pre-persist method-name="calculate"/>
    </entity>
</entity-mappings>

可以在给定实体上覆盖实体侦听器。一个实体侦听器对应于一个给定的类,一个或多个事件触发一个给定的方法调用。你还可以定义实体本身的事件,来描述回调函数。

最后但并非最不重要的是你可以定义一些默认实体侦听器,这些侦听器将首先应用于给定持久化单元的所有已映射实体的实体侦听器堆栈中。如果你不希望实体继承默认侦听器,可以使用 @ExcludeDefaultListeners(或

在完全对象/关系映射中,批量处理历来都是困难的。ORM 是关于对象状态管理的,这意味着对象状态可用于内存。但是,Hibernate具有一些优化批量处理的功能,这些功能在 Hibernate 参考指南中进行了讨论,然而,EJB3 持久性略有不同。

如前所述,自动且透明的对象/关系映射涉及对象状态的管理。这意味着对象状态可用于内存,因此直接在数据库中更新或删除(使用 SQL UPDATEDELETE)数据不会影响内存中的状态。然而,Hibernate 为批量 SQL 样式 UPDATEDELETE 语句执行提供了方法,这些方法通过 JP-QL(第 8 章,JP-QL:对象查询语言)执行。

UPDATEDELETE 语句的伪语法为:( UPDATE | DELETE ) FROM? ClassName (WHERE WHERE_CONDITIONS)?。请注意

  • 在 from 子句中,FROM 关键字是可选的。

  • from 子句中只能指定一个类名,这个类名不能具有别名(这是一个当前 Hibernate 的限制,并且很快将被移除)。

  • 批量 JP-QL 查询中不能指定联接(隐式或显式)。在 where 子句中可以使用子查询。

  • where 子句也是可选的。

例如,要执行 JP-QL UPDATE,请使用 Query.executeUpdate() 方法

EntityManager entityManager = entityManagerFactory.createEntityManager();

entityManager.getTransaction().begin();
String jpqlUpdate = "update Customer set name = :newName where name = :oldName"
int updatedEntities = entityManager.createQuery( jpqlUpdate )
                            .setParameter( "newName", newName )
                            .setParameter( "oldName", oldName )
                            .executeUpdate();
entityManager.getTransaction().commit();
entityManager.close();

要执行 JP-QL DELETE,请使用相同的 Query.executeUpdate() 方法 (该方法命名是为那些熟悉 JDBC 的 PreparedStatement.executeUpdate() 的人而命名的)

EntityManager entityManager = entityManagerFactory.createEntityManager();

entityManager.getTransaction().begin();
String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = entityManager.createQuery( hqlDelete )
                            .setParameter( "oldName", oldName )
                            .executeUpdate();
entityManager.getTransaction().commit();
entityManager.close();

Query.executeUpdate() 方法返回的 int 值表明受该操作影响的实体数量。这可能与数据库中受影响的行号相关联,也可能不相关联。例如,对于联结子类,一个 JP-QL 批量操作可能导致执行多个实际 SQL 语句。返回的数字表明受该语句影响的实际实体数量。回到联结子类的示例,针对其中一个子类的删除实际上可能导致不仅删除映射到该子类的表,而且还会删除“根”表以及继承层次结构中的联结子类表。

Java 持久性查询语言 (JP-QL) 深受 Hibernate 原生查询语言 HQL 的启发。因此,两者都非常接近于 SQL,而且可移植并且独立于数据库架构。熟悉 HQL 的人使用 JP-QL 应该没有任何问题。实际上,HQL 是 JP-QL 的严格超集,并且你对两种类型的查询都使用相同的查询 API。然而,可移植的 JPA 应用程序应该坚持使用 JP-QL。

注意

对于查询类型安全的方法,我们强烈建议你使用条件查询,请参阅 第 9 章,条件查询

您还可以使用 join 向关联实体甚至值集合的元素分配别名。

select cat, mate, kitten from Cat as cat
    inner join cat.mate as mate
    left outer join cat.kittens as kitten
select cat from Cat as cat left join cat.mate.kittens as kittens

受支持的联接类型借鉴自 ANSI SQL

可以缩写 inner joinleft outer join 结构。

select cat, mate, kitten from Cat as cat
    join cat.mate as mate
    left join cat.kittens as kitten

此外,“获取”联接允许在使用单一选择的情况下对关联或值集合进行初始化,同时初始化其父对象。这对于集合来说特别有用。它有效地覆盖了关联和集合映射元数据中的获取选项。有关更多信息,请参阅 Hibernate 参考指南的性能章节。

select cat from Cat as cat
    inner join fetch cat.mate
    left join fetch cat.kittens

获取联接通常不需要分配别名,因为关联对象不应该用于 where 子句(或任何其他子句)。此外,关联对象不会直接在查询结果中返回。相反,可以通过父对象访问它们。我们可能需要别名的唯一原因是我们正在递归联接获取另一个集合

select cat from Cat as cat 
    inner join fetch cat.mate
    left join fetch cat.kittens child
    left join fetch child.kittens

请注意,不能在使用 scroll()iterate() 调用的查询中使用 fetch 结构。也不应该将 fetchsetMaxResults()setFirstResult() 一起使用。有可能在查询中联接获取多个集合从而创建笛卡尔积(如上例所示),请小心,此积的结果不要大于您的预期。对于包映射,联接获取多个集合角色会产生意外的结果,因为 Hibernate 不可能将给定包的合法重复项与由多表笛卡尔积创建的人为重复项区别开来。

如果您正在使用属性级延迟获取(具有字节码检测),可以使用 fetch all properties 强制让 Hibernate 立即获取延迟属性(在第一个查询中)。这是 Hibernate 特有的选项

select doc from Document doc fetch all properties order by doc.name
select doc from Document doc fetch all properties where lower(doc.name) like '%cats%'

借助 where 子句,您可以缩小返回的实例列表。如果不存在别名,您可以按名称引用属性

select cat from Cat cat where cat.name='Fritz'

将返回名为“Fritz”的 Cat 实例。

select foo 
from Foo foo, Bar bar
where foo.startDate = bar.date

将返回 Foo 的所有实例,其中存在 bar 实例且其 date 属性等于 FoostartDate 属性。复合路径表达式使得 where 子句极其强大。考虑

select cat from Cat cat where cat.mate.name is not null

此查询可转换为使用表(内部)连接的 SQL 查询。如果您编写类似内容

select foo from Foo foo  
where foo.bar.baz.customer.address.city is not null

您最终将会得到一个查询,它需要在 SQL 中执行四个表连接。

可以使用 = 运算符来比较的不仅包括属性,还包括实例

select cat, rival from Cat cat, Cat rival where cat.mate = rival.mate
select cat, mate 
from Cat cat, Cat mate
where cat.mate = mate

可以使用特殊属性(小写) id 来引用对象的唯一标识符。(您还可以使用其已映射标识符属性名称。)请注意,该关键字特定于 HQL。

select cat from Cat as cat where cat.id = 123

select cat from Cat as cat where cat.mate.id = 69

第二个查询是高效的。不需要任何表连接!

也可以使用复合标识符的特性。假设人员有一个复合标识符,其中包括国家医疗保险编号

select person from bank.Person person
where person.id.country = 'AU' 
    and person.id.medicareNumber = 123456
select account from bank.Account account
where account.owner.id.country = 'AU' 
    and account.owner.id.medicareNumber = 123456

同样,第二个查询不需要表连接。

同样地,特殊属性访问多态持久性中的实例的辨别器值。嵌入在 where 子句中的 Java 类名将被转换为其辨别器值。这同样是 HQL 所特有。

select cat from Cat cat where cat.class = DomesticCat

您还可以指定组件或复合用户类型(以及组件的组件等)的属性。千万不要尝试使用以组件类型属性(而不是组件的属性)结尾的路径表达式。例如,如果store.owner是一个包含组件address的实体

store.owner.address.city    // okay
store.owner.address         // error!

一个“any”类型有特殊的属性idclass,允许我们通过以下方式表示一个联接(其中AuditLog.item是一个用<any>映射的属性)。 Any是 Hibernate 所特有

from AuditLog log, Payment payment 
where log.item.class = 'Payment' and log.item.id = payment.id

请注意,在上面的查询中log.item.classpayment.class将引用完全不同的数据库列的值。

允许在where子句中使用的表达式包括您可以在 SQL 中写入的大部分内容

inbetween 可以如下使用

select cat from DomesticCat cat where cat.name between 'A' and 'B'
select cat from DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )

否定形式可以这样写

select cat from DomesticCat cat where cat.name not between 'A' and 'B'
select cat from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )

同样, is nullis not null 可以用于测试 null 值。

通过在 Hibernate 配置中声明 HQL 查询替换,可以轻松地在表达式中使用布尔值

hibernate.query.substitutions true 1, false 0

这将用这些 HQL 中转换的 SQL 中的常量 10 替换关键字 truefalse

select cat from Cat cat where cat.alive = true

您可以使用特殊属性 size 或特殊 size() 函数(HQL 特定功能)来测试集合的大小。

select cat from Cat cat where cat.kittens.size > 0
select cat from Cat cat where size(cat.kittens) > 0

对于索引集合,您可以使用 minindexmaxindex 函数来引用最小索引和最大索引。同样,您可以使用 minelementmaxelement 函数来引用基本类型集合的最小元素和最大元素。这些是 HQL 特有功能。

select cal from Calendar cal where maxelement(cal.holidays) > current date
select order from Order order where maxindex(order.items) > 100
select order from Order order where minelement(order.items) > 10000

当传递集合的元素或索引集(elementsindices 函数)或子查询的结果时(见下文),支持 SQL 函数 any, some, all, exists, in。虽然 JP-QL 支持子查询,但 elementsindices 是特定的 HQL 功能。

select mother from Cat as mother, Cat as kit
where kit in elements(foo.kittens)
select p from NameList list, Person p
where p.name = some elements(list.names)
select cat from Cat cat where exists elements(cat.kittens)
select cat from Player p where 3 > all elements(p.scores)
select cat from Show show where 'fizard' in indices(show.acts)

请注意,这些构造 - sizeelementsindicesminindexmaxindexminelementmaxelement - 只能在 Hibernate 的 where 子句中使用。

JP-QL 允许您使用 KEY()VALUE() 操作来访问映射的键或值(即使使用 ENTRY() 访问 Entry 对象)

SELECT i.name, VALUE(p) FROM Item i JOIN i.photos p WHERE KEY(p) LIKE ‘%egret’

在 HQL 中,索引集合(数组、列表、映射)的元素可以通过索引引用(仅在 where 子句中)

select order from Order order where order.items[0].id = 1234
select person from Person person, Calendar calendar
where calendar.holidays['national day'] = person.birthDay
    and person.nationality.calendar = calendar
select item from Item item, Order order
where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11
select item from Item item, Order order
where order.items[ maxindex(order.items) ] = item and order.id = 11

方括弧 [] 中的表达式甚至可能是一个算术表达式。

select item from Item item, Order order
where order.items[ size(order.items) - 1 ] = item

HQL 还为一对多关联或值集合的元素提供了内置 index() 函数。

select item, index(item) from Order order 
    join order.items item
where index(item) < 5

可以使用基础数据库支持的标量 SQL 函数

select cat from DomesticCat cat where upper(cat.name) like 'FRI%'

如果您对所有这些还不信服,那就想想下面的查询在 SQL 中会变得更长而不那么容易读。

select cust
from Product prod,
    Store store
    inner join store.customers cust
where prod.name = 'widget'
    and store.location.name in ( 'Melbourne', 'Sydney' )
    and prod = all elements(cust.currentOrder.lineItems)

提示:类似于

SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order
FROM customers cust,
    stores store,
    locations loc,
    store_customers sc,
    product prod
WHERE prod.name = 'widget'
    AND store.loc_id = loc.id
    AND loc.name IN ( 'Melbourne', 'Sydney' )
    AND sc.store_id = store.id
    AND sc.cust_id = cust.id
    AND prod.id = ALL(
        SELECT item.prod_id
        FROM line_items item, orders o
        WHERE item.order_id = o.id
            AND cust.current_order = o.id
    )

Hibernate 查询功能强大且复杂。事实上,查询语言的功能是 Hibernate 的主要卖点(现在是 JP-QL)之一。以下是与我最近一个项目中使用的查询非常类似的一些示例查询。请注意,您编写的多数查询都会比这些简单得多!

以下查询返回某个特定客户的所有未付款订单的订单 ID、产品数量和订单总价值,并且给定最小总价值,按总价值对结果排序。在确定价格时,它使用当前目录。针对 ORDERORDER_LINEPRODUCTCATALOGPRICE 表的结果 SQL 查询具有四个内部联接和一个(非相关的)子查询。

select order.id, sum(price.amount), count(item)
from Order as order
    join order.lineItems as item
    join item.product as product,
    Catalog as catalog
    join catalog.prices as price
where order.paid = false
    and order.customer = :customer
    and price.product = product
    and catalog.effectiveDate < sysdate
    and catalog.effectiveDate >= all (
        select cat.effectiveDate 
        from Catalog as cat
        where cat.effectiveDate < sysdate
    )
group by order
having sum(price.amount) > :minAmount
order by sum(price.amount) desc

太恐怖了!老实说,在现实生活中,我对子查询并不是很热衷,因此我的查询实际上更像是这样

select order.id, sum(price.amount), count(item)
from Order as order
    join order.lineItems as item
    join item.product as product,
    Catalog as catalog
    join catalog.prices as price
where order.paid = false
    and order.customer = :customer
    and price.product = product
    and catalog = :currentCatalog
group by order
having sum(price.amount) > :minAmount
order by sum(price.amount) desc

下一个查询统计了每个状态中的支付数量,其中不包括 AWAITING_APPROVAL 状态,且最近的状态更改由当前用户进行的所有支付。它转换为一条 SQL 查询,其中包含两个内部连接和一个针对 PAYMENTPAYMENT_STATUSPAYMENT_STATUS_CHANGE 表的相关子选择。

select count(payment), status.name 
from Payment as payment 
    join payment.currentStatus as status
    join payment.statusChanges as statusChange
where payment.status.name <> PaymentStatus.AWAITING_APPROVAL
    or (
        statusChange.timeStamp = ( 
            select max(change.timeStamp) 
            from PaymentStatusChange change 
            where change.payment = payment
        )
        and statusChange.user <> :currentUser
    )
group by status.name, status.sortOrder
order by status.sortOrder

如果我将 statusChanges 集合映射为列表而不是集合,则查询将变得更易于编写。

select count(payment), status.name 
from Payment as payment
    join payment.currentStatus as status
where payment.status.name <> PaymentStatus.AWAITING_APPROVAL
    or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUser
group by status.name, status.sortOrder
order by status.sortOrder

然而,该查询将特定于 HQL。

下一个查询使用 MS SQL Server isNull() 函数返回当前用户所属组织的所有帐户和未支付的付款。它转换为一条 SQL 查询,其中包含三个内部连接、一个外部连接和一个针对 ACCOUNTPAYMENTPAYMENT_STATUSACCOUNT_TYPEORGANIZATIONORG_USER 表的子选择。

select account, payment
from Account as account
    join account.holder.users as user
    left outer join account.payments as payment
where :currentUser = user
    and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID)
order by account.type.sortOrder, account.accountNumber, payment.dueDate

Criteria 查询是一种以编程、类型安全的方式表示查询的方法。它们在使用接口和类表示查询的各种结构部分(例如查询本身、选择子句或 order-by 等)方面是类型安全的。正如我们马上可以看到的那样,它们在引用属性方面也可以是类型安全的。旧版本 Hibernate 中 org.hibernate.Criteria 查询 API 的用户会认可一般做法,虽然我们相信 JPA API 是更高一等的,因为它是对从该 API 学到的经验教训的清晰解读。

criteria 查询本质上是一个对象图,其中图的各个部分表示查询中越来越 atomic 的部分(当我们在该图中向下移动)。执行 criteria 查询的第一步是构建此图。要开始使用 criteria 查询,首先需要熟悉 javax.persistence.criteria.CriteriaBuilder 接口。它的作用是所有 criteria 各个部分的工厂。您可以通过调用 javax.persistence.EntityManagerFactorygetCriteriaBuilder 方法来获取 javax.persistence.criteria.CriteriaBuilder 实例。

CriteriaBuilder builder = entityManagerFactory.getCriteriaBuilder();

下一步是获取 javax.persistence.criteria.CriteriaQuery。您可以使用 javax.persistence.criteria.CriteriaBuilder 中的 3 个方法来执行此操作。

CriteriaQuery<T> createQuery(Class<T>)
CriteriaQuery<Tuple> createTupleQuery()
CriteriaQuery<Object> createQuery()

根据查询结果的预期类型,每个方法都有不同的作用。

注意

第 6 章 criteria API [JPA 2 规范] 中已包含有关 criteria 查询各个部分的相当数量的参考资料。因此,与其在此处复制所有内容,不如查看 API 中一些更广泛预期的用法。

CriteriaQuery<T> createQuery(Class<T>)

criteria 查询的类型(又名 <T>)指示查询结果中的预期类型。这可以是实体、Integer 或任何其他对象。

选择值的最简单形式是从实体中选择特定属性。但这也可以是聚合、数学运算等。



实际上,有几种不同的方法可以使用条件查询选择多个值。我们将在本文中探讨 2 个选项,但另一种推荐的方法是使用元组,如 第 9.2 节“元组条件查询” 中所述



对于第 9.1.3 节“选择多个值”,更好的方法要么是使用包装器(我们刚刚在 第 9.1.4 节“选择一个包装器” 中看到了),要么使用 javax.persistence.Tuple 合约。


javax.persistence.Tuple 合约提供了 3 种基本形式来访问底层元素

类型的
<X> X get(TupleElement<X> tupleElement)

这允许以类型化方式访问底层元组元素。我们在示例 9.7,“选择一个元组”tuple.get( idPath )tuple.get( agePath ) 调用中看到了这一点。几乎所有东西都是 javax.persistence.TupleElement

位置的
Object get(int i)
<X> X get(int i, Class<X> type)

和我们看到 示例 9.4“选择一个数组”示例 9.5“选择一个数组(2)” 的位置访问方面的描述非常类似。只有这里提供的第二个形式提供了类型化,因为用户在访问时明确提供了类型。我们在 示例 9.7“选择一个元组” 中的 tuple.get( 0 )tuple.get( 1 ) 调用中看到了这点。

alias
Object get(String alias)
<X> X get(String alias, Class<X> type)

再次,只有这里提供的第二个形式提供了类型化,因为用户在访问时明确提供了类型。我们没有看到使用它的示例,但这是微不足道的。举例来说,我们可以简单地将别名应用于其中任一路径,如 idPath.alias( "id" ) 和/或 agePath.alias( "age" ),我们便可以通过那些指定的别名访问各个元组元素。

 

CriteriaQuery 对象定义了对一个或多个实体、可嵌入或基本抽象架构类型的查询。该查询的根对象是实体,而其他类型则是通过导航从该实体达成的。

 
 --[JPA 2 规范,第 6.5.2 节查询根,第 262 页]

根定义了所有联接、路径和属性在查询中可用的基础。在条件查询中,根始终是一个实体。根由 javax.persistence.criteria.CriteriaQuery 上的重载 from 方法对条件进行定义和添加

<X> Root<X> from(Class<X>)
<X> Root<X> from(EntityType<X>)

条件查询可以定义多个根,其效果是在新添加的根和其他根之间创建一个笛卡尔乘积。以下是匹配所有单身男性和所有单身女性的示例

CriteriaQuery query = builder.createQuery();

Root<Person> men = query.from( Person.class );
Root<Person> women = query.from( Person.class );
Predicate menRestriction = builder.and(
    builder.equal( men.get( Person_.gender ), Gender.MALE ),
    builder.equal( men.get( Person_.relationshipStatus ), RelationshipStatus.SINGLE )
);
Predicate womenRestriction = builder.and(
    builder.equal( women.get( Person_.gender ), Gender.FEMALE ),
    builder.equal( women.get( Person_.relationshipStatus ), RelationshipStatus.SINGLE )
);
query.where( builder.and( menRestriction, womenRestriction ) );

还可以用数据库的本地 SQL 方言来表达查询。当你想要利用数据库特定特性(如查询提示或 Oracle 中的 CONNECT BY 选项)时,这十分有用。它还为基于 SQL/JDBC 的应用程序提供了从直接 SQL 迁移到 Hibernate 的清晰路径。请注意,Hibernate 允许你为所有创建、更新、删除和加载操作指定手写 SQL(包括存储过程)(有关详细信息,请查阅参考指南)。

若要使用 SQL 查询,则需要描述 SQL 结果集,此描述将帮助 EntityManager 将你的列映射到实体属性。此操作使用 @SqlResultSetMapping 注释来完成。每个 @SqlResultSetMapping 都有一个名称,在 EntityManager 上创建 SQL 查询时使用该名称。

@SqlResultSetMapping(name="GetNightAndArea", entities={

    @EntityResult(name="org.hibernate.test.annotations.query.Night", fields = {
        @FieldResult(name="id", column="nid"),
        @FieldResult(name="duration", column="night_duration"),
        @FieldResult(name="date", column="night_date"),
        @FieldResult(name="area", column="area_id")
    }),
    @EntityResult(name="org.hibernate.test.annotations.query.Area", fields = {
        @FieldResult(name="id", column="aid"),
        @FieldResult(name="name", column="name")
    })
    }
)
//or
@SqlResultSetMapping(name="defaultSpaceShip", entities=@EntityResult(name="org.hibernate.test.annotations.query.SpaceShip"))

此外,可以定义标量结果,甚至可以将实体结果与标量结果混合

@SqlResultSetMapping(name="ScalarAndEntities",

    entities={
        @EntityResult(name="org.hibernate.test.annotations.query.Night", fields = {
            @FieldResult(name="id", column="nid"),
            @FieldResult(name="duration", column="night_duration"),
            @FieldResult(name="date", column="night_date"),
            @FieldResult(name="area", column="area_id")
        }),
        @EntityResult(name="org.hibernate.test.annotations.query.Area", fields = {
            @FieldResult(name="id", column="aid"),
            @FieldResult(name="name", column="name")
        })
    },
    columns={
        @ColumnResult(name="durationInSec")
    }
)

然后 SQL 查询必须返回列别名 durationInSec

有关 @SqlResultSetMapping. 的更多信息,请参阅 Hibernate Annotations 参考指南。