天天看点

Hibernate|Hibernate5.4用户指南(五)之持久化上下文-Persistence Context

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 可能存在也可能不存在 是,数据库计划删除

各状态转换图如下:

Hibernate|Hibernate5.4用户指南(五)之持久化上下文-Persistence Context

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()方法的对象必须处于持久态。

5.5 获取实体引用而不初始化其数据

5.6 获取已初始化其数据的实体

5.7 通过id获取多个实体

5.8 通过Natural-id获取实体

5.9 过滤实体和关联

5.10 修改实体持久态

5.11 刷新实体状态

5.12 使用游离态实体的数据

5.13 检查持久态

5.14 驱逐实体

5.15 级联实体状态转换

5.16 异常处理