Hibernate 的表/子类继承策略的效率

我正在考虑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 和 setters 以及 Hibernate 映射等,只是假设它们是基本的明显情况)。

这些实体的数据库表是有意义的,你可以得到很好的非规范化等等。但是,Hibernate会执行什么查询来提取单个动物?我可以想到至少两种可能发生这种情况的情况:

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

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


如果你有混凝土类型的亚型,事情也会变得更毛茸茸的:

public class Greyhound extends Dog {
   float lifetimeRacingWinnings;
}

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

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


答案 1

您会发现 Hibernate 使用一系列语句(每个子类一个语句)为未知动物类型编写查询。因此,随着子类数量的增加,查询速度会变慢,并且会尝试返回更广泛的结果集。所以你是对的,它不能很好地扩展大型类层次结构。LEFT JOIN

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

我还没有尝试过多层继承。如果上述内容还没有让你失望,建议你试试看 - 你可以打开SQL调试输出来查看发送到数据库的内容,或者只是分析你的数据库。


答案 2

David M的有用答案之后,我决定进行一个骨架测试。

我在三级层次结构中创建了一个抽象的超类和25个具体子类(我希望你能猜到它们的名字)。每个类都有一个整数字段,其名称与其字母相对应 - 例如,类除了从其直接父级继承的字段和来自顶级抽象超类的和字段外,还有一个int字段。ADTestAADTestGgbADTestBpkeya

发出 HQL 查询会产生以下 SQL:from ADTestA where pkey=1

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

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

因此,看起来这些查询将非常昂贵。我会考虑需要它们的频率(例如,与知道我想要一个实例并立即要求其中一个实例(仅在所需的父表中连接)进行比较)。然而,我有一种感觉,从其他实体引用这是不可避免的;换句话说,来自类型字段的一对一映射总是会涉及这种查找。ADTestPADTestA

(另一方面,替代策略也不是希望的灯塔;走每个层次结构的表格路线并在单个表格中拥有数百列听起来也不是很有效......)


推荐