Hibernate5.4官方用户指南
1 Hibernate架构
……
5 持久化上下文
org.hibernate.Session API、javax.persistence.EntityManager API这两个API代表了处理持久性数据的上下文。持久性数据具有与持久性上下文和底层数据库相关的状态。状态如下:
-
transient(or new)-瞬时态
实体刚刚被实例化,并且与持久化上下文无关。它在数据库中没有持久表示,并且通常没有分配标识符值即id(除非使用了指定的生成器)。
-
persistent(or managed)-持久态
该实体具有关联的标识符并与持久性上下文相关联。它可能存在或可能不存在于数据库中。
-
detached-游离态
该实体具有关联的标识符,但不再与持久性上下文关联(通常是因为持久性上下文已关闭或实例已从上下文中逐出)
-
removed-移除态(新增状态)
该实体具有关联的标识符并与持久性上下文相关联,但是,它被安排从数据库中删除。
状态 | 有无id | 是否与持久性上下文关联 | 是否存在于持久性上下文中 | 是否存在于数据库 |
---|---|---|---|---|
transient | 通常无,除非使用了指定的生成器 | 否 | 否 | 否 |
persistent | 有 | 是 | 是 | 可能存在也可能不存在 |
detached | 有 | 否 | 否 | / |
removed | 有 | 是 | 可能存在也可能不存在 | 是,数据库计划删除 |
各状态转换图如下:
5.1 从JPA访问Hibernate API
从JPA访问Hibernate API
Session session = entityManager.unwrap( Session.class );
SessionImplementor sessionImplementor = entityManager
.unwrap( SessionImplementor.class );
SessionFactory sessionFactory = entityManager
.getEntityManagerFactory().unwrap( SessionFactory.class );
5.2 字节码增强
Hibernate 3.x在Hibernate中首次尝试了字节码增强支持,在此之前Hibernate只支持基于代理的延迟加载,并且总是使用基于diff的脏计算。
此处介绍Hibernate5.0以后对字节码增强的支持。
5.2.1 功能
Hibernate支持增强Java领域模型,以便将各种与持久性相关的功能直接添加到类中。
延迟属性加载
将此视为部分加载支持。从本质上讲,你可以告诉Hibernate,只有在从数据库中获取时才加载实体的一部分,以及何时加载其他部分。请注意,这与基于代理的延迟加载概念有很大不同,后者是以实体为中心的,其中根据需要立即加载实体的状态。通过字节码增强,可以根据需要加载单个属性或属性组。
可以指定延迟属性一起加载,这称为“懒惰组”。默认情况下,所有单数属性都是单个组的一部分,这意味着当访问一个惰性单数属性时,将加载所有惰性单数属性。默认情况下,惰性复数属性本身就是一个惰性组。使用 @org.hibernate.annotations.LazyGroup 注解可以显式控制延迟属性加载。如下:
@Entity
public class Customer {
@Id
private Integer id;
private String name;
@Basic( fetch = FetchType.LAZY )
private UUID accountsPayableXrefId;
@Lob
@Basic( fetch = FetchType.LAZY )
@LazyGroup( "lobs" )
private Blob image;
//Getters and setters are omitted for brevity
}
上面所示代码中,有2个延迟加载的属性:accountsPayableXrefId、image。这两个属性属于不同的“懒惰组”(accountsPayableXrefId是默认提取组的一部分),这意味着访问accountsPayableXrefId不会强制加载image属性,反之亦然。
作为一种有可能的扩展,目前要求所有懒惰的单一关联(多对一和一对一)也包括在内@LazyToOne(LazyToOneOption.NO_PROXY)。后续Hibernate版本可能放宽这个要求。
在线脏跟踪
Hibernate3.x以前,Hibernate只支持基于diff的脏计算方法来确定持久化上下文中的哪些实体已更改。
即Hibernate会跟踪实体关于数据库的最后已知状态(通常是最后一次读取或写入)。然后,作为刷新持久化上下文的一部分,Hibernate将遍历与持久化上下文关联的每个实体,并针对“最后已知的数据库状态”检查其当前状态。这是迄今为止最彻底的脏检查方法,因为它考虑了可以改变其内部状态的数据类型(这java.util.Date是其中的主要示例)的情况。但是,在具有大量关联实体的持久化上下文中,它可能是一种低性能的方法。
如果不用考虑“内部状态更改数据类型”的情况,则字节码增强的脏跟踪可能是值得考虑的替代方案,尤其是在性能方面。
在这种方法中,Hibernate将操纵类的字节码,直接向实体添加“脏跟踪”,允许实体本身跟踪其属性的变化。在刷新时,Hibernate会询问您的实体发生了哪些更改,而不是执行状态差异计算。
双向关联管理
Hibernate努力使您的应用程序尽可能接近“普通Java使用”(惯用Java)。
双向关联:
@Entity(name = "Person")
public static class Person {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "author")
private List<Book> books = new ArrayList<>();
//Getters and setters are omitted for brevity
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
@NaturalId
private String isbn;
@ManyToOne
private Person author;
//Getters and setters are omitted for brevity
}
示例1.常用的Java的错误用法:
Person person = new Person();
person.setName( "John Doe" );
Book book = new Book();
person.getBooks().add( book );
try {
book.getAuthor().getName();
}
catch (NullPointerException expected) {
// This blows up ( NPE ) in normal Java usage
}
示例2.常用的Java的正确用法:
Person person = new Person();
person.setName( "John Doe" );
Book book = new Book();
person.getBooks().add( book );
book.setAuthor( person );
book.getAuthor().getName();
字节码增强的双向关联管理使得第1个示例通过在操纵一侧时管理双向关联的“另一侧”来工作。
内部性能优化
我们使用增强过程添加一些额外的代码,使我们能够优化持久化上下文的某些性能特征。
5.2.2 执行增强
运行时增强
目前,只有在JPA定义的SPI执行类转换后的托管JPA环境中才支持领域模型的运行时增强。默认情况下禁用此支持。
要启用运行时增强,请指定以下配置属性之一:
-
hibernate.enhancer.enableDirtyTracking(true或false(默认值))
在运行时字节码增强中启用脏跟踪功能。
-
hibernate.enhancer.enableLazyInitialization(true或false(默认值))
在运行时字节码增强中启用延迟加载功能。这样,即使是基本类型(例如@Basic(fetch = FetchType.LAZY)也可以懒惰地获取。
-
hibernate.enhancer.enableAssociationManagement(true或false(默认值))
在运行时字节码增强中启用关联管理功能,仅在更改一侧时自动同步双向关联。
目前只有使用注解标注的类支持运行时增强。
Gradle
Hibernate提供了一个Gradle插件,它能够提供域模型的构建时增强功能,因为它们是作为Gradle构建的一部分编译的。要使用该插件,项目首先需要应用它如下:
ext {
hibernateVersion = 'hibernate-version-you-want'
}
buildscript {
dependencies {
classpath "org.hibernate:hibernate-gradle-plugin:$hibernateVersion"
}
}
hibernate {
enhance {
// any configuration goes here
}
}
可用的配置通过已注册的Gradle DSL扩展公开:
-
enableLazyInitialization
是否应该进行延迟属性加载的增强。
-
enableDirtyTracking
是否应该进行自我跟踪增强。
-
enableAssociationManagement
否应该进行双向关联管理的增强。
以上3个配置的默认值为 false。、
enhance { }需要该块才能进行增强。默认情况下禁用增强功能以准备插件中的添加功能(hbm2ddl等)。
Maven插件
Hibernate提供了一个Maven插件,能够提供域模型的构建时增强功能,因为它们是作为Maven构建的一部分编译的。
有关配置设置的详细信息,请参阅Gradle插件部分。同样,那3个配置的默认值是false。
Maven插件支持一个额外的配置设置:failOnError,它控制发生错误时发生的情况。默认行为是使构建失败,但可以设置它以便仅发出警告。
如下应用Maven插件:
<build>
<plugins>
[...]
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>$currentHibernateVersion</version>
<executions>
<execution>
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>true</enableDirtyTracking>
<enableAssociationManagement>true</enableAssociationManagement>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
[...]
</plugins>
</build>
5.3 持久化实体
使用JPA API持久化实体:-entityManager.persist()
Person person = new Person();
person.setId( 1L );
person.setName("John Doe");
entityManager.persist( person );
使用Hibernate API持久化实体:-session.save()
Person person = new Person();
person.setId( 1L );
person.setName("John Doe");
session.save( person );
org.hibernate.Session还有一个名为persist的方法,它遵循JPA规范中为persist方法定义的确切语义。这是javax.persistence.EntityManager委托Hibernate org.hibernate.Session实现委托的方法。
如果实体类型的属性有生成的标识符,则在调用save或persist时,该标识符值与实例关联。如果未自动生成标识符,则必须在调用save或persist方法之前在实例上设置手动分配的(通常是自然的)键值。
5.4 删除实体
使用JPA API删除实体:
entityManager.remove( person );
使用Hibernate API删除实体:
session.delete( person );
Hibernate本身可以处理处于游离态的实体。但是,JPA不允许这种行为。这里的含义是传递给org.hibernate.Session.delete()方法的对象可以处于持久态或游离态,而传递给javax.persistence.EntityManager.remove()方法的对象必须处于持久态。