Hibernate的每子类表inheritance策略的效率

我正在考虑Hibernate管理的类层次结构的表布局,当然每个子类技术的表格在一般意义上都是最合适的。 但是,通过逻辑思考我对其性能有一些担忧,特别是随着子类数量的增加。

为了给出一个非常简短(和经典)的例子,假设您有以下类:

public abstract class Animal { int pkey; String name; } public class Dog extends Animal { long numSlippersChewed; // int is not large enough... } public class Cat extends Animal { short miceCaught; // ... but here int is far bigger than required :-) } 

(我正在消除getter和setter以及Hibernate映射等,只是假设它们是基本明显的情况)。

这些实体的数据库表是有意义的,你得到了很好的非规范化等等。 但是,Hibernate为了掏出一只动物做了什么查询呢? 我可以想到至少有两种可能发生这种情况的情况:

  1. 一些其他实体具有一对一(或一对多)映射,例如Human类的pet字段。 这将存储pkey,因此当Hibernate获取Human对象时,它也需要获取相应的Animal对象。 当给出动物的密钥时,Hibernate将使用什么查询(/ ies)来提取和解组实际的动物数据,因为它可以驻留在CatDog表中?
  2. HQL,例如from Animal where name='Rex' (我们假设名称是唯一的)。 这类似于上面的内容,它允许您在超类表中标识一行,但您不知道要检查哪个子类表以获取更多详细信息。 HQL甚至允许您from抽象类发出查询吗? (使用子类特定的东西可以很好地工作,例如from Cat where miceCaught > 5 )。

我可以想到两种方法可以在SQL中完成,而且看起来都不漂亮。 一种是在给定pkey的每个子类表上运行exists查询,然后从返回命中的表中加载。 或者,Hibernate可以在所有表​​中执行一些可怕的联合查询 – 实质上模拟每层次表的方案,因为结果集将包括所有可能子类的属性,子类表中的各个选择为不相关的参数返回null 。 后一种情况甚至可能需要添加一个合成鉴别器列,以便Hibernate可以知道哪个子类表实际返回了行,从而知道应该将哪些Java类解析成。


如果您有混凝土类型的子类型,事情也会变得更加繁荣:

 public class Greyhound extends Dog { float lifetimeRacingWinnings; } 

现在对于给定的动物pkey, Dog Greyhound表中可能存在有效行,这意味着我手动检查对应于pkey的类的第一种方法变得更加困难。

我之所以如此关注的原因是我希望在类层次结构上使用这种方法,其中包含大约70个类,最大嵌套链为4-5级,因此对所有这些进行联合查询可能会很糟糕性能。 Hibernate是否有任何技巧可以保持相对高效? 或者是通过pkey加载对其中一个类的引用需要很长时间?

您会发现Hibernate使用一系列LEFT JOIN语句为未知动物类型编写查询,每个子类一个。 因此,随着子类数量的增加,查询将变慢,并将尝试返回更宽的结果集。 所以你是对的,它不适合大型类层次结构。

使用HQL,是的,您可以直接查询子类,并访问其属性。 然后将使用单个INNER JOIN进行渲染。

我没有尝试过多级inheritance。 如果上面还没有让你失望,建议你试试看 – 你可以打开SQL调试输出来查看发送到数据库的内容,或者只是简介你的数据库。

在David M的有用回答之后,我决定将骨架测试放在一起。

我在三级层次结构中创建了一个抽象超类, ADTestA和25个具体的子类(我希望你可以猜出它们的名字)。 每个类都有一个整数字段,其名称对应于其字母 – 例如,除了从其直接父ADTestBinheritance的b字段外,类ADTestG还有一个int字段gADTestB和顶部a字段级抽象超类。

from ADTestA where pkey=1发出HQL查询from ADTestA where pkey=1导致以下SQL:

 select adtesta0_.pkey as pkey0_, adtesta0_.a as a0_, adtesta0_1_.b as b1_, adtesta0_2_.c as c2_, adtesta0_3_.d as d3_, adtesta0_4_.e as e4_, adtesta0_5_.f as f5_, adtesta0_6_.g as g6_, adtesta0_7_.h as h7_, adtesta0_8_.i as i8_, adtesta0_9_.j as j9_, adtesta0_10_.k as k10_, adtesta0_11_.l as l11_, adtesta0_12_.m as m12_, adtesta0_13_.n as n13_, adtesta0_14_.o as o14_, adtesta0_15_.p as p15_, adtesta0_16_.q as q16_, adtesta0_17_.r as r17_, adtesta0_18_.s as s18_, adtesta0_19_.t as t19_, adtesta0_20_.u as u20_, adtesta0_21_.v as v21_, adtesta0_22_.w as w22_, adtesta0_23_.x as x23_, adtesta0_24_.y as y24_, adtesta0_25_.z as z25_, case when adtesta0_6_.pkey is not null then 6 when adtesta0_7_.pkey is not null then 7 when adtesta0_8_.pkey is not null then 8 when adtesta0_9_.pkey is not null then 9 when adtesta0_10_.pkey is not null then 10 when adtesta0_11_.pkey is not null then 11 when adtesta0_12_.pkey is not null then 12 when adtesta0_13_.pkey is not null then 13 when adtesta0_14_.pkey is not null then 14 when adtesta0_15_.pkey is not null then 15 when adtesta0_16_.pkey is not null then 16 when adtesta0_17_.pkey is not null then 17 when adtesta0_18_.pkey is not null then 18 when adtesta0_19_.pkey is not null then 19 when adtesta0_20_.pkey is not null then 20 when adtesta0_21_.pkey is not null then 21 when adtesta0_22_.pkey is not null then 22 when adtesta0_23_.pkey is not null then 23 when adtesta0_24_.pkey is not null then 24 when adtesta0_25_.pkey is not null then 25 when adtesta0_1_.pkey is not null then 1 when adtesta0_2_.pkey is not null then 2 when adtesta0_3_.pkey is not null then 3 when adtesta0_4_.pkey is not null then 4 when adtesta0_5_.pkey is not null then 5 when adtesta0_.pkey is not null then 0 end as clazz_ from ADTestA adtesta0_ left outer join ADTestB adtesta0_1_ on adtesta0_.pkey=adtesta0_1_.pkey left outer join ADTestC adtesta0_2_ on adtesta0_.pkey=adtesta0_2_.pkey left outer join ADTestD adtesta0_3_ on adtesta0_.pkey=adtesta0_3_.pkey left outer join ADTestE adtesta0_4_ on adtesta0_.pkey=adtesta0_4_.pkey left outer join ADTestF adtesta0_5_ on adtesta0_.pkey=adtesta0_5_.pkey left outer join ADTestG adtesta0_6_ on adtesta0_.pkey=adtesta0_6_.pkey left outer join ADTestH adtesta0_7_ on adtesta0_.pkey=adtesta0_7_.pkey left outer join ADTestI adtesta0_8_ on adtesta0_.pkey=adtesta0_8_.pkey left outer join ADTestJ adtesta0_9_ on adtesta0_.pkey=adtesta0_9_.pkey left outer join ADTestK adtesta0_10_ on adtesta0_.pkey=adtesta0_10_.pkey left outer join ADTestL adtesta0_11_ on adtesta0_.pkey=adtesta0_11_.pkey left outer join ADTestM adtesta0_12_ on adtesta0_.pkey=adtesta0_12_.pkey left outer join ADTestN adtesta0_13_ on adtesta0_.pkey=adtesta0_13_.pkey left outer join ADTestO adtesta0_14_ on adtesta0_.pkey=adtesta0_14_.pkey left outer join ADTestP adtesta0_15_ on adtesta0_.pkey=adtesta0_15_.pkey left outer join ADTestQ adtesta0_16_ on adtesta0_.pkey=adtesta0_16_.pkey left outer join ADTestR adtesta0_17_ on adtesta0_.pkey=adtesta0_17_.pkey left outer join ADTestS adtesta0_18_ on adtesta0_.pkey=adtesta0_18_.pkey left outer join ADTestT adtesta0_19_ on adtesta0_.pkey=adtesta0_19_.pkey left outer join ADTestU adtesta0_20_ on adtesta0_.pkey=adtesta0_20_.pkey left outer join ADTestV adtesta0_21_ on adtesta0_.pkey=adtesta0_21_.pkey left outer join ADTestW adtesta0_22_ on adtesta0_.pkey=adtesta0_22_.pkey left outer join ADTestX adtesta0_23_ on adtesta0_.pkey=adtesta0_23_.pkey left outer join ADTestY adtesta0_24_ on adtesta0_.pkey=adtesta0_24_.pkey left outer join ADTestZ adtesta0_25_ on adtesta0_.pkey=adtesta0_25_.pkey where adtesta0_.pkey=1 

这不是很漂亮,并且确实对应于我希望可以避免的每层次表的有效模拟。

所以看起来这些查询会非常昂贵。 我会考虑一下他们需要多ADTestP (比如说,知道我想要一个ADTestP的实例,并且要求其中一个实际上只是加入所需的父表)。 然而,我有一种感觉,这对于其他实体的参考是不可避免的; 换句话说,来自ADTestA类型字段的一对一映射总是涉及这种查找。

(另一方面,替代策略也没有发出希望的信号;按照每层次的表格路由,并且在单个表格中有数百个列,听起来也不是很有效……)

只要您通过Hibernate访问您的数据库并且您没有重要数据或准备编写一个小的迁移脚本,您就应该能够在开发过程中很晚才能对每个子类/层次结构的表做出决定。 这是ORM的美妙之处,它抽象了数据库结构……

另一方面,我是“喜欢构图而不是inheritance”的忠实粉丝(更喜欢inheritance作品而不是inheritance? )而且我很怀疑一个有超过4-5级的70个class级的模型不能简化……但是我让你自己思考那个,毕竟我不知道你想要什么样的探索。