重要提示:持久性上下文从不在不同的 JTA 事务或不是由相同实体管理器工厂生成的实体管理器之间共享。在使用扩展持久性上下文进行上下文传播时,有一些值得注意的例外情况
下载 Hibernate Core 发行版。设置类路径(在你最喜欢的 IDE 中创建新项目后)
<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.jar
和 validation-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>
<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>
名称
事务类型
提供程序
提供程序是 EJB 持久性提供程序的完全限定类名。如果你不使用多个 EJB3,则不必定义它。当你使用 EJB 持久性多个供应商实现时,这是必需的。
jta-data-source
, non-jta-data-source
映射文件
jar 文件
<jar-file>file:/home/turin/work/local/lab8/build/classes</jar-file>
exclude-unlisted-classes
类
默认情况下,如果使用 @Cacheable
进行注释,则实体将被选为二级缓存。但是你可以
不幸的是,DDL
并非标准模式(但非常有用),并且你无法将其放入 <validation-mode>
中。要使用它,请添加常规属性
<property name="javax.persistence.validation.mode">
ddl
</property>
<property name="javax.persistence.validation.mode">
ddl, callback
</property>
properties
properties 元素用于指定特定于供应商的属性。这是定义特定于 Hibernate 的配置的地方。还需要在此指定 JDBC 连接信息。
以下列出 JPA 2 标准属性。请务必参阅 Hibernate Core 的文档,以了解特定于 Hibernate 的属性。
<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 规范定义了一个引导程序,用于访问 EntityManagerFactory
和 EntityManager
。引导类是 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
定义使用的交易类型(JTA
或 RESOURCE_LOCAL
)
javax.persistence.jtaDataSource
定义 JNDI 中的 JTA 数据源名称
javax.persistence.nonJtaDataSource
定义 JNDI 中的非 JTA 数据源名称
javax.persistence.lock.timeout
毫秒为单位的悲观锁超时(Integer
或 String
)
javax.persistence.query.timeout
毫秒为单位的查询超时(Integer
或 String
)
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 提供程序中只有几个属性可用。
请注意,可以在同一配置中混合使用 XML <class>
声明和 hibernate.ejb.cfgfile
。注意潜在的冲突。在 persistence.xml
中设置的属性将覆盖在定义的 hibernate.cfg.xml
中的属性。
重要的是,不要覆盖 hibernate.transaction.factory_class
,Hibernate EntityManager 会根据 EntityManager 类型自动设置适当的事务工厂(即 JTA
与 RESOURSE_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
请注意,如果安全性未启用,将删除 JACC*EventListeners
。
您可以通过属性(请参阅 配置和引导)或 ejb3configuration.getEventListeners()
API 配置事件监听器。
实体管理器工厂应被视为不可变配置持有者,它被定义为指向一个数据源和映射一组已定义实体。这是创建和管理 EntityManager
的入口点。 Persistence
类是创建实体管理器工厂的引导类。
// Use persistence.xml configuration
EntityManagerFactory emf = Persistence.createEntityManagerFactory("manager1")
EntityManager em = emf.createEntityManager(); // Retrieve an application managed entity manager
// Work with the EM
em.close();
...
emf.close(); //close at application end
就像在 Hibernate 中一样(比较术语括号中),一个实体实例处于以下状态之一
EntityManager
API 可以更改实体的状态,或者换句话说,可以加载和存储对象。如果你考虑对象状态管理,而不是管理 SQL 语句,你会发现使用 JPA 进行持久化更容易理解。
一旦创建了一个新的实体实例(使用常见的 new
运算符),它将处于 new
状态。可以通过将其与实体管理器相关联来使其持久
DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
em.persist(fritz);
使用实体管理器的 find()
方法,按其标识符值加载实体实例
cat = em.find(Cat.class, catId);
// You may need to wrap the primitive identifiers
long catId = 1234;
em.find( Cat.class, new Long(catId) );
在某些情况下,你并不真正希望加载对象状态,而仅仅拥有对其的引用(即代理)。你可以使用 getReference()
方法获取此引用。这在不必加载父对象的情况下将子对象链接到其父对象时特别有用。
child = new Child();
child.SetName("Henry");
Parent parent = em.getReference(Parent.class, parentId); //no query to the DB
child.setParent(parent);
em.persist(child);
em.persist(cat);
em.flush(); // force the SQL insert and triggers to run
em.refresh(cat); //re-read the state (after the trigger executes)
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();
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 方法。虽然它更冗长,但它提供编译器强制的安全(包括属性名称),当应用程序将切换到维护模式时,它将会非常有益。
如果使用投影,JPA 查询可以返回对象元组。每个结果元组作为对象数组返回
Iterator kittensAndMothers = sess.createQuery(
"select kitten, mother from Cat kitten join kitten.mother mother")
.getResultList()
.iterator();
while ( kittensAndMothers.hasNext() ) {
Object[] tuple = (Object[]) kittensAndMothers.next();
Cat kitten = (Cat) tuple[0];
Cat mother = (Cat) tuple[1];
....
}
如果您需要在结果集上指定界限(想要检索的最大行数和/或想要的检索的第一行),请使用以下方法
Query q = em.createQuery("select cat from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.getResultList(); //return cats from the 20th position to 29th
@javax.persistence.NamedQuery(name="eg.DomesticCat.by.name.and.minimum.weight",
query="select cat from eg.DomesticCat as cat where cat.name = ?1 and cat.weight > ?2")
Query q = em.createNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
q.setString(1, name);
q.setInt(2, minWeight);
List<?> cats = q.getResultList();
Query q = em.createNamedQuery("eg.DomesticCat.by.name.and.minimum.weight", Cat.class);
q.setString(1, name);
q.setInt(2, minWeight);
List<Cat> cats = q.getResultList();
请注意,实际程序代码不依赖于所使用的查询语言,您还可以元数据中定义本机 SQL 查询,或通过将它们放入 XML 映射文件中,使用 Hibernate 的本机功能。
您可以在执行查询时调整所使用的刷新模式,以及定义用于加载实体的锁模式。
当必须确保查询执行不会触发刷新操作时,调整刷新模式很有意思。通常,您不用关心这一点。
如果您需要将查询返回的对象锁定到某个级别,调整锁模式非常有用。
query.setFlushMode(FlushModeType.COMMIT)
.setLockMode(LockModeType.PESSIMISTIC_READ);
Cat cat = em.find( Cat.class, new Long(69) );
cat.setName("PK");
em.flush(); // changes to cat are automatically detected and persisted
有时此编程模型效率低下,因为它需要在同一会话中执行 SQL SELECT(加载对象)和 SQL UPDATE(保存其更新状态)。因此,Hibernate 提供了一种使用分离实例的备用方法。
当对象在持久化上下文中加载时,它将由 Hibernate 进行管理。您可以通过关闭 EntityManager 或通过调用 detach()
方法来强制分离对象(即不再由 Hibernate 管理)。
Cat cat = em.find( Cat.class, new Long(69) );
...
em.detach(cat);
cat.setName("New name"); //not propatated to the database
JPA 规范通过使用 EntityManager.merge()
方法提供对分离实例进行修改的持久化来支持此开发模型
// in the first entity manager
Cat cat = firstEntityManager.find(Cat.class, catId);
Cat potentialMate = new Cat();
firstEntityManager.persist(potentialMate);
// in a higher layer of the application
cat.setMate(potentialMate);
// later, in a new entity manager
secondEntityManager.merge(cat); // update cat
secondEntityManager.merge(mate); // update mate
merge()
方法将对分离实例进行的修改合并到相应的受管实例(如果有)中,而不考虑持久化上下文的态。换句话说,如果已经存在,则合并的对象状态将覆盖持久化上下文中持久化实体的状态。如果应用程序仅希望它们的态也持久化,则应分别 merge()
可从给定分离实例访问的分离实例。这可以使用传递持久化,级联到关联的实体和集合,请参见传递持久化。
合并操作非常智能,可以自动检测是否必须以插入或更新为结果来合并分离实例。换句话说,您不必担心将新实例(而不是分离实例)传递给 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
实体管理器会不时执行所需的 SQL DML 语句,以同步数据存储与内存中保存的对象状态。该过程称为刷新。
刷新会自动在以下时间点发生(这是 Hibernate 特有的,并且不在规范中定义)
(异常:使用由应用程序分配的标识符的实体实例在保存时插入。)
可以通过更改默认行为来减少刷新频率。实体管理器的 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 核心参考文档。
保存、删除或重新附加各个对象非常繁琐,尤其是当您处理相关对象的图形时。亲子关系是一种常见情况。考虑以下示例
@OneToOne(cascade=CascadeType.PERSIST)
@OneToOne(cascade= { CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH } )
您甚至可以使用 CascadeType.ALL
指定应为特定关联级联所有操作。请记住,默认情况下,不级联任何操作。
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
entityManager.get(Cat.class, catId);
...
boolean isIn = entityManager.contains(cat);
assert isIn;
您还可以检查对象、关联或属性是否为延迟加载。可以独立于底层持久性提供程序执行此操作
PersistenceUtil jpaUtil = Persistence.getPersistenceUtil();
if ( jpaUtil.isLoaded( customer.getAddress() ) {
//display address if loaded
}
if ( jpaUtil.isLoaded( customer.getOrders ) ) {
//display orders if loaded
}
if (jpaUtil.isLoaded(customer, "detailedBio") ) {
//display property detailedBio if loaded
}
但是,如果您有权访问 entityManagerFactory,我们建议您使用
PersistenceUnitUtil jpaUtil = entityManager.getEntityManagerFactory().getPersistenceUnitUtil();
Customer customer = entityManager.get( Customer.class, customerId );
if ( jpaUtil.isLoaded( customer.getAddress() ) {
//display address if loaded
}
if ( jpaUtil.isLoaded( customer.getOrders ) ) {
//display orders if loaded
}
if (jpaUtil.isLoaded(customer, "detailedBio") ) {
//display property detailedBio if loaded
}
log.debug( "Customer id {}", jpaUtil.getIdentifier(customer) );
元模型本身在 [JPA 2 规范] 的第 5 章 元模型 API 中进行描述。[JPA 2 规范] 的第 6 章 准则 API 描述并展示在准则查询中对元模型的使用,正如 第 9 章,准则查询 一样。
元模型是描述您的域模型的一组对象。 javax.persistence.metamodel.Metamodel
充当这些元模型对象的储存库并提供对它们的访问权限,可以通过 javax.persistence.EntityManagerFactory
或 javax.persistence.EntityManager
及其 getMetamodel
方法获取。
此元模型非常重要,共有 2 个方面。首先,它允许提供程序和框架以通用方式处理应用程序的域模型。持久性提供程序已经拥有某种形式的元模型,他们使用该元模型来描述被映射的域模型。然而,此 API 为对现有信息定义了单一的、独立的访问。例如,验证框架可使用此信息来理解关联;封送框架或许会使用此信息来决定封送实体关系图的多少。此用法超出了本说明文档的范围。
截至今日,JPA 2 元模型不提供任何用于访问与物理模型相关的关系信息的功能。预计此问题将在规范的未来版本中得到解决。
其次,从应用程序编写者的角度,它允许非常流畅地表达完全类型安全的条件查询,尤其是静态元模型方法。[JPA 2 规范]定义了一些元模型的访问和使用方式,其中包括我们将稍后阐述的静态元模型方法。如果代码具有领域模型的先验知识,则静态元模型方法非常棒。第 9 章,条件查询在其示例中独家使用这种方法。
静态元模型是一系列类,它们“镜像”领域模型中的实体和嵌入对象,并且提供对镜像类属性的元数据的静态访问。我们只讨论 [JPA 2 规范] 称之为 规范元模型 的内容
要导入必要的 javax.persistence.metamodel 类型,必须包括导入声明(例如, | ||
-- [JPA 2 规范,第 6.2.1.1 节,第 198-199 页] |
如果愿意,这些规范化元模型类可以手动生成,但预计大多数开发人员更愿意使用注释处理器。注释处理器本身不在本文档的讨论范围之内。但是,Hibernate 团队确实开发了一个注释处理器工具来生成规范化元模型。请参见Hibernate 元模型生成器。
构建 Hibernate EntityManagerFactory
时,它将为所知道的每个受管理类型查找规范化元模型类,如果找到任何规范化元模型类,它将向其中注入适当的元模型信息,如 [JPA 2 规范,第 6.2.2 节,第 200 页] 所述
有关 Hibernate 实体管理器和并发控制最重要的要点在于它非常易于理解。Hibernate 实体管理器直接使用 JDBC 连接和 JTA 资源,而不会添加任何附加的锁定行为。我们强烈建议您花一些时间了解 JDBC、ANSI 和数据库管理系统的隔离规范。Hibernate 实体管理器只添加自动版本控制,但不锁定内存中的对象或更改数据库事务的隔离级别。基本上,您使用 Hibernate 实体管理器的方式与使用数据库资源的直接 JDBC(或 JTA/CMT)的方式相同。
我们从EntityManagerFactory
和EntityManager
以及数据库事务和长期工作单元的粒度开始讨论 Hibernate 中的并发控制。
在本章中,除非明确表示,我们将会混合匹配实体管理器和持久性上下文的概念。一个是 API 和编程对象,另一个是范围定义。不过,请记住它们之间的本质区别。持久性上下文通常在 Java EE 中绑定到 JTA 事务,且持久性上下文在事务边界时开始和结束(事务范围),除非您使用扩展实体管理器。有关更多信息,请参阅第 1.2.3 节“持久性上下文范围”。
EntityManagerFactory
是一个创建成本高昂、线程安全的对象,旨在供应用程序中所有线程共享。它在应用程序启动时通常只创建一次。
要完成此概念,您还必须考虑数据库事务。数据库事务必须尽量简短,以减少数据库中的锁定争用。长时间的数据库事务将阻止您的应用程序扩展到高并发负载。
工作单元的范围是什么?一个 Hibernate EntityManager
可以跨越多个数据库事务,还是它们之间是一对一的关系?您什么时候应该打开和关闭Session
,以及如何划分数据库事务边界?
从用户的角度来看,我们称此工作单元为长期运行 应用程序事务。有很多方法可用于在应用程序中实现此事务。
每个请求一个实体管理器带分离对象和每个应用程序事务一个实体管理器都有优点和缺点,我们将在本章后面在乐观并发控制的背景下对此进行讨论。
持久性上下文会缓存管理状态的每个对象(由 Hibernate 监视并检查脏状态)。这意味着如果长时间开放或仅仅加载太多数据,它会无休止地增长,直到您收到 OutOfMemoryException
。解决此问题的一种方法是对持久性上下文进行某种批处理以定期刷新,但如果您需要海量数据操作,您应该考虑使用数据库存储过程。针对此问题的某些解决方法在 第 7 章,批量处理 中显示。在用户会话期间保持持久性上下文开放还意味着过时数据发生的概率很高,您必须了解这一点并适当控制。
如果 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 块外关闭。
如果您使用 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();
}
如果您在 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()
返回的任何未关闭的 ScrollableResults
或 Iterator
实例。您必须通过从 finally
块显式调用 ScrollableResults.close()
或 Hibernate.close(Iterator)
来释放基础数据库游标。(当然,大多数应用程序都可以轻松避免在 CMT 代码中使用 scroll()
或 iterate()
。)
以这种方式定义的所有应用程序管理实体管理器和容器管理持久化上下文都是 EXTENDED
。这意味着持久化上下文类型超出事务生命周期。我们应该了解在事务范围之外进行的操作会有什么后果。
// 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();
在 EXTENDED
持久性上下文中,在活动事务之外进行的所有操作都会排队。在活动事务中(最坏情况下在提交时间)执行时将刷新 EXTENDED
持久性上下文。
// 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
应用程序通常需要针对持久性机制中发生的特定事件作出反应。这允许实现特定类型的通用功能,并扩展内置功能。JPA 规范为此提供了两种相关机制。
@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() );
}
}
回调方法可以引发 RuntimeException
。如果存在当前事务,则必须回滚。定义了以下回调
回调方法不得调用 EntityManager
或 Query
方法!
您可以在不同级别层次结构的每个实体上定义多个实体侦听器。您还可以在不同级别的层次结构上定义多个回调。但您不能在同一个实体或同一个实体侦听器中为同一个事件定义两个侦听器。
可以使用 @ExcludeSuperclassListeners
停止实体侦听器的继承,然后所有超类的 @EntityListeners
都将被忽略。
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>
可以在给定实体上覆盖实体侦听器。一个实体侦听器对应于一个给定的类,一个或多个事件触发一个给定的方法调用。你还可以定义实体本身的事件,来描述回调函数。
在完全对象/关系映射中,批量处理历来都是困难的。ORM 是关于对象状态管理的,这意味着对象状态可用于内存。但是,Hibernate具有一些优化批量处理的功能,这些功能在 Hibernate 参考指南中进行了讨论,然而,EJB3 持久性略有不同。
如前所述,自动且透明的对象/关系映射涉及对象状态的管理。这意味着对象状态可用于内存,因此直接在数据库中更新或删除(使用 SQL UPDATE
和 DELETE
)数据不会影响内存中的状态。然而,Hibernate 为批量 SQL 样式 UPDATE
和 DELETE
语句执行提供了方法,这些方法通过 JP-QL(第 8 章,JP-QL:对象查询语言)执行。
UPDATE
和 DELETE
语句的伪语法为:( 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 章,条件查询。
select c from eg.Cat c
这将返回类 eg.Cat
的所有实例。与 HQL 不同,选择子句在 JP-QL 中不是可选的。我们通常不需要限定类名,因为实体名称默认为未限定的类名 (@Entity
)。所以,我们几乎总是只写
select c from Cat c
你可能已经注意到,你可以为类分配别名,as
关键字是可选的。别名允许你在查询的其他部分中引用 Cat
。
select cat from Cat as cat
select from, param from Formula as form, Parameter as param
您还可以使用 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
可以缩写 inner join
、left outer join
结构。
select cat, mate, kitten from Cat as cat join cat.mate as mate left join cat.kittens as kitten
select cat from Cat as cat inner join fetch cat.mate left join fetch cat.kittens
select cat from Cat as cat inner join fetch cat.mate left join fetch cat.kittens child left join fetch child.kittens
如果您正在使用属性级延迟获取(具有字节码检测),可以使用 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%'
select mate from Cat as cat inner join cat.mate as mate
查询将选择其他 Cat
的 mate
。实际上,您可以更简洁地表示此查询为
select cat.mate from Cat cat
select cat.name from DomesticCat cat where cat.name like 'fri%'
select cust.name.firstName from Customer as cust
查询可以返回多个对象和/或属性,形式为 Object[]
类型的数组,
select mother, offspr, mate.name from DomesticCat as mother inner join mother.mate as mate left outer join mother.kittens as offspr
select new list(mother, offspr, mate.name) from DomesticCat as mother inner join mother.mate as mate left outer join mother.kittens as offspr
或形式为实际类型安全的 Java 对象(通常称为视图对象),
select new Family(mother, mate, offspr) from DomesticCat as mother join mother.mate as mate left join mother.kittens as offspr
select max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n from Cat cat
当与 select new map
结合使用时这非常有用(HQL 特有特性)
select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n ) from Cat cat
select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat) from Cat cat
您可以在选择子句中使用算术运算符、字符串连接和已知的 SQL 函数(根据所配置的方言,HQL 特有特性)
select cat.weight + sum(kitten.weight) from Cat cat join cat.kittens kitten group by cat.id, cat.weight
select firstName||' '||initial||' '||upper(lastName) from Person
可以按 SQL 中的方式使用 distinct
和 all
关键字,它们具有相同的语义。
select distinct cat.name from Cat cat select count(distinct cat.name), count(cat) from Cat cat
select cat from Cat as cat
from java.lang.Object o // HQL only
from Named n, Named m where n.name = m.name // HQL only
请注意,后两个查询需要一个以上的 SQL SELECT
。这意味着 order by
子句未正确对整个结果集进行排序。(它还表示您不能使用 Query.scroll()
调用这些查询。)
借助 where
子句,您可以缩小返回的实例列表。如果不存在别名,您可以按名称引用属性
select cat from Cat cat where cat.name='Fritz'
select foo from Foo foo, Bar bar where foo.startDate = bar.date
将返回 Foo
的所有实例,其中存在 bar
实例且其 date
属性等于 Foo
的 startDate
属性。复合路径表达式使得 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”类型有特殊的属性id
和class
,允许我们通过以下方式表示一个联接(其中AuditLog.item
是一个用<any>
映射的属性)。 Any
是 Hibernate 所特有
from AuditLog log, Payment payment where log.item.class = 'Payment' and log.item.id = payment.id
允许在where
子句中使用的表达式包括您可以在 SQL 中写入的大部分内容
in
、not in
、between
、is null
、is not null
、is empty
、is not empty
、member of
和not member of
“简单”情况、case ... when ... then ... else ... end
和“被搜索”情况、case when ... then ... else ... end
second(...)
、minute(...)
、hour(...)
、day(...)
、month(...)
、year(...)
(特定于 HQL)
任何函数或运算符: substring(), trim(), lower(), upper(), length(), locate(), abs(), sqrt(), bit_length()
cast(...as ...)
,其中第二个参数是 Hibernate 类型的名称,如果底层数据库支持 ANSI cast()
和 extract()
,则为 extract(...from ...)
针对日期的 JDBC 转义语法(取决于 JDBC 驱动程序支持)(例如 where date = {d '2008-12-31'}
)
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 null
和 is not null
可以用于测试 null 值。
通过在 Hibernate 配置中声明 HQL 查询替换,可以轻松地在表达式中使用布尔值
hibernate.query.substitutions true 1, false 0
这将用这些 HQL 中转换的 SQL 中的常量 1
和 0
替换关键字 true
和 false
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
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
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)
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
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 )
select cat.color, sum(cat.weight), count(cat) from Cat cat group by cat.color
select foo.id, avg(name), max(name) from Foo foo join foo.names name group by foo.id
select cat.color, sum(cat.weight), count(cat) from Cat cat group by cat.color having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
如果基础数据库(例如 MySQL)支持,则在 having
和 order by
子句中允许使用 SQL 函数和聚合函数。
select cat from Cat cat join cat.kittens kitten group by cat having avg(kitten.weight) > 100 order by count(kitten) asc, sum(kitten.weight) desc
对于支持子查询的数据库,JP-QL 支持查询中的子查询。子查询必须用括号括起来(通常是 SQL 聚合函数调用)。即使是相关子查询(引用外部查询中别名的子查询)也是允许的。
select fatcat from Cat as fatcat where fatcat.weight > ( select avg(cat.weight) from DomesticCat cat )
select cat from DomesticCat as cat where cat.name = some ( select name.nickName from Name as name )
select cat from Cat as cat where not exists ( from Cat as mate where mate.mate = cat )
select cat from DomesticCat as cat where cat.name not in ( select name.nickName from Name as name )
select cat from Cat as cat where not ( cat.name, cat.color ) in ( select cat.name, cat.color from DomesticCat cat )
请注意,在某些数据库中(但不是 Oracle 或 HSQLDB),可以在其他上下文中使用元组构造函数,例如查询组件或复合用户类型时
select cat from Person where name = ('Gavin', 'A', 'King')
select cat from Person where name.first = 'Gavin' and name.initial = 'A' and name.last = 'King')
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
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
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
select usr.id, usr.name from User as usr left join usr.messages as msg group by usr.id, usr.name order by count(msg)
如果你的数据库支持子选择,则可以在 where 子句中根据选择大小设置条件
from User usr where size(usr.messages) >= 1
select usr.id, usr.name from User usr.name join usr.messages msg group by usr.id, usr.name having count(msg) >= 1
由于内部连接无法返回包含零条消息的 User
,因此以下形式也很有用
select usr.id, usr.name from User as usr left join usr.messages as msg group by usr.id, usr.name having count(msg) = 0
Criteria 查询是一种以编程、类型安全的方式表示查询的方法。它们在使用接口和类表示查询的各种结构部分(例如查询本身、选择子句或 order-by 等)方面是类型安全的。正如我们马上可以看到的那样,它们在引用属性方面也可以是类型安全的。旧版本 Hibernate 中 org.hibernate.Criteria
查询 API 的用户会认可一般做法,虽然我们相信 JPA API 是更高一等的,因为它是对从该 API 学到的经验教训的清晰解读。
criteria 查询本质上是一个对象图,其中图的各个部分表示查询中越来越 atomic 的部分(当我们在该图中向下移动)。执行 criteria 查询的第一步是构建此图。要开始使用 criteria 查询,首先需要熟悉 javax.persistence.criteria.CriteriaBuilder
接口。它的作用是所有 criteria 各个部分的工厂。您可以通过调用 javax.persistence.EntityManagerFactory
的 getCriteriaBuilder
方法来获取 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 或任何其他对象。
这是 Hibernate 查询语言 (HQL) 和 Hibernate Criteria 查询中使用最广泛的查询方式。您有一个实体,并且希望根据某个条件选择一个或多个该实体。
示例 9.1. 选择根实体
CriteriaQuery<Person> criteria = builder.createQuery( Person.class ); Root<Person> personRoot = criteria.from( Person.class ); criteria.selec
t( personRoot ); criteria.where
( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); List<Person> people = em.createQuery( criteria ).getResultList(); for ( Person p
erson : people ) { ... }
我们在此处使用格式 createQuery( Person.class ),因为在开始处理结果时,我们发现预期返回实际上是 Person 实体。 | |
personCriteria.select( personRoot ) 在此特定情况下完全不需要,因为 personRoot 将成为隐式选择,因为我们只有一个根。在这里,只执行此操作是为了实现示例的完整性 | |
Person_.eyeColor 是静态形式元模型引用的示例。我们将在本章中专门使用该形式。请参阅 第 4.1 节,“静态元模型” 了解详细信息。 |
选择值的最简单形式是从实体中选择特定属性。但这也可以是聚合、数学运算等。
示例 9.3. 选择表达式
CriteriaQuery<Integer> criteria = builder.createQuery( Integer.class ); Root<Person> personRoot = criteria.from( Person.class ); criteria.selec
t( builder.max( personRoot.get( Person_.age ) ) ); criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); Integer maxAge = em.createQuery( criteria ).getSingleResult();
这里我们看到 |
实际上,有几种不同的方法可以使用条件查询选择多个值。我们将在本文中探讨 2 个选项,但另一种推荐的方法是使用元组,如 第 9.2 节“元组条件查询” 中所述
示例 9.5. 选择数组 (2)
CriteriaQuery<Object[]> criteria = builder.createQuery( Object[].class ); Root<Person> personRoot = criteria.from( Person.class ); Path<Long> idPath = personRoot.get( Person_.id ); Path<Integer> agePath = personRoot.get( Person_.age ); criteria.multi
select( idPath, agePath ); criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); List<Object[]>
valueArray = em.createQuery( criteria ).getResultList(); for ( Object[] values : valueArray ) { final Long id = (Long) values[0]; final Integer age = (Integer) values[1]; ... }
就像我们在示例 9.4“选择数组”中看到的那样,我们有一个返回对象数组的“类型化”CriteriaQuery。 | |
这实际上与我们在示例 9.4“选择数组”中看到的内容完全相同。 |
查询条件类型的另一种选择是第 9.1.3 节“选择多个值”。相反选择一个将“包装”多个值的Object。回到示例查询中,不要返回[Person#id, Person#age]数组,而是声明一个包含这些值并返回它们的类。
对于第 9.1.3 节“选择多个值”,更好的方法要么是使用包装器(我们刚刚在 第 9.1.4 节“选择一个包装器” 中看到了),要么使用 javax.persistence.Tuple
合约。
示例 9.7. 选择一个元组
CriteriaQuery<Tuple> criteria = builder.createTupleQuery(); Root<Person> personRoot = criteria.from( Person.class ); Path<Long> idPath = personRoot.get( Person_.id ); Path<Integer> agePath = personRoot.get( Person_.age ); criteria.multi
select( idPath, agePath ); criteria.where( builder.equal( personRoot.get( Person_.eyeColor ), "brown" ) ); List<Tuple> tu
ples = em.createQuery( criteria ).getResultList(); for ( Tuple tuple : valueArray ) { assert tup
le.get( 0 ) == tuple.get( idPath ); assert tup
le.get( 1 ) == tuple.get( agePath ); ... }
此处,我们看到了一个新的 | |
我们再次看到了 | |
此处,我们看到 |
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 ) 调用中看到了这点。
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 页] |
<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 ) );
示例 9.10. 具有集合的示例
CriteriaQuery<Person> personCriteria = builder.createQuery( Person.class );
Root<Person> personRoot = person.from( Person.class );
Join<Person,Order> orders = personRoot.join( Person_.orders );
Join<Order,LineItem> orderLines = orders.join( Order_.lineItems );
...
正如在 HQL 和 EJB-QL 中一样,我们可以指定关联的数据与所有者一起预提取。预提取由 javax.persistence.criteria.From
接口的众多重载 fetch
方法创建
从技术上讲,嵌入式属性始终与其所有者一起预提取。然而,为了定义 Address#country 的预提取,我们需要对其父路径使用 javax.persistence.criteria.Fetch
。
示例 9.12. 集合示例
CriteriaQuery<Person> personCriteria = builder.createQuery( Person.class );
Root<Person> personRoot = person.from( Person.class );
Join<Person,Order> orders = personRoot.fetch( Person_.orders );
Join<Order,LineItem> orderLines = orders.fetch( Order_.lineItems );
...
还可以用数据库的本地 SQL 方言来表达查询。当你想要利用数据库特定特性(如查询提示或 Oracle 中的 CONNECT BY 选项)时,这十分有用。它还为基于 SQL/JDBC 的应用程序提供了从直接 SQL 迁移到 Hibernate 的清晰路径。请注意,Hibernate 允许你为所有创建、更新、删除和加载操作指定手写 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 参考指南。
String sqlQuery = "select night.id nid, night.night_duration, night.night_date, area.id aid, "
+ "night.area_id, area.name from Night night, Area area where night.area_id = area.id "
+ "and night.night_duration >= ?";
Query q = entityManager.createNativeQuery(sqlQuery, "GetNightAndArea");
q.setParameter( 1, expectedDuration );
q.getResultList();
此原生查询依据 GetNightAndArea
结果集返回夜晚和区域。
String sqlQuery = "select * from tbl_spaceship where owner = ?";
Query q = entityManager.createNativeQuery(sqlQuery, SpaceShip.class);
q.setParameter( 1, "Han" );
q.getResultList();
版权 © 2005 Red Hat Inc.和各作者