2010-01-07 55 views
13

我正在考虑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 :-) 
} 

(我eliding getter和setter和Hibernate映射等等,仅仅假设他们是基本明显的情况)。

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

  1. 有一个对一个(或一个一对多)映射,如Human类的pet领域的一些其他实体。这将存储pkey,所以当Hibernate获取一个Human对象时,它也需要获取相应的Animal对象。当给定动物的pkey时,Hibernate会使用什么查询来提取和解组实际动物数据,因为它可能位于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; 
} 

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

我非常担心的原因是我会希望在类层次结构上使用这种方法,最多有70个类,最大嵌套链的级别为4-5,因此对所有这些进行联合查询是可能会有可怕的表现。 Hibernate有没有什么小窍门可以保持这种相对高性能?或者正在通过pkey加载对这些类中的一个的引用需要很长时间?

+0

也许你想看到http://stackoverflow.com/questions/2700680/table-per-subclass-inheritance-relationship-how-to-query-against-the-parent-clas – 2010-06-18 01:49:40

+0

出于好奇我不知道是否其他一些基于JPA对象的存储系统也有同样的问题(即ObjectDB)。换句话说,也许标准的关系数据库可能不是最好的技术,特别是因为你有一个巨大的对象图。 – 2010-12-25 15:36:38

回答

8

你会发现Hibernate用一系列LEFT JOIN语句为每个子类写一个未知动物类型的查询。所以查询会随着子类数量的增加而减慢,并且会试图返回更广泛的结果集。所以你是正确的,它不能很好地扩展大类的层次。

有了HQL,你可以直接查询子类,并访问它的属性。然后将会用一个INNER JOIN进行渲染。

我还没有尝试过这种多层次的继承。如果上述内容还没有让你失望,建议你尝试一下,看看 - 你可以打开SQL调试输出来查看发送到数据库的内容,或者简单地配置你的数据库。

+0

谢谢 - 我考虑过测试自己,但由于某种原因,如果没有用于性能测试的实际数据,这并不值得,并且我不想以我的担忧开始这种方式。然而,假设测试用例至少让我看到所使用的技术,并将其从一个Hibernate性能问题减少到一个SQL性能问题,这应该更容易推理。 – 2010-01-07 14:18:23

+0

参考你的第三段:由于我发布的SQL显示,多层次的继承似乎没有太大的变化。据我所知,唯一的区别是用于生成合成鉴别符的'case'语句中的子句的顺序。所以从性能角度来看,这应该是相同的。 (当然,当具体实例化一个子类时,会有更多的表加入,但这并不关我太多)。 – 2010-01-07 18:16:51

+0

将其标记为自反思以来就已被接受,它清楚地回答了我所问的问题,即使它没有提供我所期望的魔力。 – 2010-01-08 15:42:21

3

David M's helpful answer之后我决定拼凑一个骨架测试。

我在三级层次结构中创建了一个抽象超类ADTestA和25个具体子类(我希望你可以猜出它们的名字)。每个类都有一个整数字段,其名称与其字母相对应 - 例如,类ADTestG除了b字段外,还有一个int字段g,它从其直接父节点ADTestBpkeya字段继承级抽象超类。

发出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的实例,并要求其中一个刚刚加入所需的父表中)。不过,我有一种感觉,从其他实体的引用来看,这是不可避免的。换句话说,来自类型为ADTestA的字段的一对一映射始终会涉及到这种查找。 (另一方面,替代策略也不希望带来希望的灯塔;按照层次结构的路线,并且在单个表中实际上有数百个列,听起来也不是很有效......)

+0

+1反馈 - 谢谢。 – 2010-01-07 16:44:33

1

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

另一方面,我是“喜欢构图而不是继承”(Prefer composition over inheritance?)的一个粉丝,我很可疑的是一个模型与70 4-5级别的课程不能简化......但我会让你自己思考那个问题,毕竟我不知道你试图解决什么问题。

+0

不幸的是,这些表格主要是通过Hibernate访问的,但对于偶尔访问的其他非Java工具来说,设计至少是必须的。我可能会在这里混淆关注点的相关性,但是在引入新表的同时,类层次结构正在重构,数据库性能是主要动力之一。我在某种程度上同意抽象问题,但是Joel写了抽象漏洞;如果在Hibernate中无法以高性能的方式完成这项工作,我理想的是尽可能早地发现它。 – 2010-01-07 16:41:23

+0

哦 - 我确实考虑写一份关于合法子类数量的免责声明,因为我认为有人会强调它是一种气味。 :-)继续动物比喻,我正在编写类似于动物园或兽医的代码,所以我们合法地有一个'Animal'>'脊椎动物'>'哺乳动物'''食肉动物''''犬''''狗'类型对于许多不同的动物(是的,在层次结构的每个级别都有有用的行为)。使用这个类结构的Java代码比它所取代的几乎平坦的代码更好,更干净。 – 2010-01-07 16:50:18