Hibernate.org社区文档

第 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

在 Hibernate 中(可比较的术语括在括号中),实体实例处于以下状态之一

EntityManager API 允许您更改实体状态,换句话说,允许您加载和存储对象。如果您考虑对象状态管理,而不是 SQL 语句管理,您会发现 JPA 中的持久化更易于理解。

一旦使用通用 new 操作符创建了一个新实体实例,则该实例处于 new 状态。您可以通过将它与实体管理器关联到使它持久化

DomesticCat fritz = new DomesticCat();

fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
em.persist(fritz);

如果 DomesticCat 实体类型具有已生成的标识符,则当调用 persist() 时,该值会与该实例相关联。如果未自动生成标识符,则必须在调用 persist() 之前在实例上设置该应用程序分配(通常为自然值)键值。

使用实体管理器的 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.refresh() 操作重新加载实体实例及其集合。当使用数据库触发器初始化实体的某些属性时,这很有用。请注意,只有实体实例及其集合被刷新,除非您将 REFRESH 指定为任何关联的级联样式

em.persist(cat);

em.flush(); // force the SQL insert and triggers to run
em.refresh(cat); //re-read the state (after the trigger executes)

如果您不知道要查找对象的标识符值,则需要一个查询。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 查询 中说明的 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 方法。虽然更冗长,但它提供编译器强制的安全(包括属性名称),当应用程序移动到维护模式时,这将得到回报。

事务管理的实例(即由实体管理器加载、保存、创建或查询的对象)可以由应用程序操作,并且在刷新实体管理器时(稍后在本章中讨论)将持久该持久状态的任何更改。无需调用特定方法来使修改持久。更新实体实例的状态的一种直接方式是 find() 它,然后在持久化上下文打开时对其进行直接操作

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 管理。您可以强制分离对象(即不再由 Hibernate 管理),方法是关闭 EntityManager 或通过调用 detach() 方法以更细粒度的方式进行分离。

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

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

通常在以下情况下使用 merge()

下面是 merge() 的确切语义

EntityManager.remove() 会从数据库中删除对象状态。当然,您的应用程序仍可能保存对已删除对象的引用。您可以将 remove() 看作是再次让持久实例变为新实例(又称瞬态实例)。它不是分离的状态,而且合并将导致插入。

实体管理器会定期执行需要的 SQL DML 语句,以同步存储中的数据和内存中持有的对象的 state。此过程称为刷新。

刷新在以下点默认发生(这是 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 core 参考文档。

保存、删除或重新附加各个对象相当繁琐,尤其是在处理一组关联对象时。亲子关系就是一个典型案例。请考虑以下示例

如果亲子关系中的子关系是值类型(例如地址或字符串集合),它们的声明周期将取决于父级,而且不再需要进一步操作即可方便地“级联”状态更改。当保存父级时,将同时保存值类型的子级对象;当删除父级时,将同时删除子级对象,依此类推。即使对于诸如从集合中删除子级元素之类的操作,这也同样有效;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 注释文档以获取更多信息。

Hibernate 提供更多本机级联选项,请参阅 Hibernate Annotations 手册以及 Hibernate 参考指南以获取更多信息。

建议

你可以定义各种级别的锁定策略。可以有以下几种方式应用锁定

可以使用不同的锁定方法

所有这些锁定都会防止给定实体上的脏读和不可重复读。乐观锁定会尽可能晚地强制锁定,希望在此过程中没有人更改底层数据;而悲观锁定会立即强制锁定,并保持锁定直到提交事务。

在启用二级缓存后(请参见 第 2.2.1 节 “软件包”和 Hibernate Annotations 参考文档),Hibernate 会确保使用和正确更新缓存。但是,你可以通过传递两个属性来调整这些设置

  • javax.persistence.cache.retrieveMode 接受 CacheRetrieveMode

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

CacheRetrieveMode 控制 Hibernate 如何从二级缓存中访问信息:默认的 USE 或意味着忽略高速缓存的 BYPASSCacheStoreMode 控制 Hibernate 如何将信息推送到二级缓存:默认且在从数据库中读取和写入数据库时将数据推送到高速缓存中的 USE、不将新数据插入高速缓存(但可以使过时数据失效)的 BYPASS 以及与默认类似但即使数据已被缓存也会强制将数据推送到高速缓存中的 REFRESH

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

  • 通过 setProperty 方法在特定 EntityManager

  • 通过查询提示(setHint 方法)在查询中

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

JPA 还引入了查询二级高速缓存和手动清除数据的 API。

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) );

这样做性能可能稍微好一点,还可以从对象中获取标识符值(使用 getIdentifier())。

始终可以从给定的 EntityManager 回退到底层的 Session API

Session session = entityManager.unwrap(Session.class);