Hibernate ORM 版本 6.6
已发布 Alpha 版本,最终版本将很快推出。在今天的文章中,我们将深入了解该版本的新特性之一,即新的 @ConcreteProxy
注解。
问题
尽管实体代理在大多数情况下都透明地工作,作为 Hibernate 的用户,你不需要做任何特殊的事情,但有些情况下,它们的操作方式可能与普通实体实例不同。当一个关联是 多态 的,即它引用了一个具有子类型的实体类型时,代理并不了解它所代表的实体实例的具体子类型。子类型只有在代理被检索并且目标表已经被读取之后才知道。这显然在依赖于 Java 的 instanceof
运算符和类型转换时会导致问题。
考虑以下简单的实体映射
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "animal_type")
class Animal {
@Id
@GeneratedValue
Long id;
Long getId() {
return id;
}
}
class Cat extends Animal {
String name;
String getName() {
return name;
}
}
class Owner {
@Id
Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "animal_id")
Animal animal;
}
当加载一个 Owner
实例时,其 animal
属性将是一个 代理 实例
Cat cat = new Cat();
cat.name = "Bella";
Owner owner = new Owner();
owner.id = 1L;
owner.animal = cat;
// later
Owner owner = session.find( Owner.class, 1L );
Hibernate.isInitialized( owner.animal ) // returns false
owner.animal instanceof Animal; // returns true
((Animal) owner.animal).getId(); // returns the id
owner.animal instanceof Cat; // returns false
((Cat) owner.animal).getName(); // throws ClassCastException
注意生成的 SQL 从未访问 Animal
表
select
c1_0.id,
c1_0.lazy_id
from
Owner c1_0
where
sp1_0.id=1
以前,解决这个问题唯一的方法是使用 Hibernate 类的静态实用方法,如 getClassLazy()
和 unproxy()
,但它们会在处理继承层次结构时导致早期初始化。
Hibernate 团队决定为我们提供一种与具有继承功能的实体类型一起工作的代理的替代方法,并保证它们将始终以适当的子类型创建。
解决方案
我们引入了新的@ConcreteProxy
注解:当该注解放置在实体继承层次结构的根节点上时,这将告诉Hibernate在创建懒加载代理实例时始终解析实际实体类型。从之前的例子来看,这意味着
Owner owner = session.find( Owner.class, 1L );
owner.animal instanceof Cat; // returns true
Cat cat = (Cat) owner.animal;
Hibernate.isInitialized( cat ); // returns false, laziness is preserved
cat.getName(); // returns the Cat's name
懒加载的animal
关联仍然包含一个未初始化的代理,但这次它尊重与已加载的Owner
实例关联的实际子类型。这意味着懒加载将被保留,而任何instanceof
检查或显式类型转换现在将按预期工作。
此功能并非免费:为了确定创建代理实例时要使用的具体类型,Hibernate可能需要访问实体的表来发现与特定标识符值对应的实际子类型。 |
这次之前的查询将包括与Animal
表的left join
,用于读取区分符值
select
o1_0.id,
o1_0.animal_id,
a1_0.animal_type
from
Owner o1_0
left join
Animal a1_0
on a1_0.id=o1_0.animal_id
where
op1_0.id=1
具体类型将被确定
-
在单表继承中,当获取关联时,区分符列值被左连接,或者当获取引用时,直接从实体表中读取。
-
当使用连接继承时,必须左连接所有子类型表以确定具体类型。请注意,然而,当使用显式区分符列时,行为与单表继承相同。
-
最后,对于表按类继承,必须查询所有子类型表以确定具体类型。
以下是一个用于检索请求懒引用时Animal
的具体类型的查询示例
select
a1_0.animal_type
from
Animal a1_0
where
a1_0.id=1
有关更多信息和支持,您可以参考我们的Jira上的原始功能请求。
接下来是什么
为了绕过每次创建代理时都需要通过left join
访问懒加载关联的目标表的需求,Hibernate可以将区分符值直接存储在所有者端表上,包括外键本身。这种区分符值的反规范化将使@ConcreteProxy
关联检索更有效,同时保留其有关instanceof
检查和类型转换的功能保证。
如果您想让我们知道您对这个新功能有何看法,或者如果您对此有任何疑问,请通过常规渠道与我们联系。