Hibernate支持三种继承映射策略:
1.每个具体类一张表
把域模型中的每个实体对象类映射到一个单独的表中,这是最简单的映射方式。对过这种方式将域模型映射为关系模型两个步骤 :
在使用这种方法实现域模型到关系开模型之间的转换时需要注意,在这个转换策略中,并没有单独处理对象之间的继承关系,而是通过不同的表来分别实现这些实体对象。这也就造成了这种映射方法并不能真正完全体现对象之间的关系。
对于使用每个具体类一张表的映射策略, 在创建 Hibernate 映射配置文件的时候也有两种配置方法可以使用。
其中一种配置方法是使用,如下所示:
<class name="Payment"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="sequence"/> </id> <property name="amount" column="AMOUNT"/> ... <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </union-subclass> <union-subclass name="CashPayment" table="CASH_PAYMENT"> ... </union-subclass> <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> ... </union-subclass> </class>
使用这种方式除了每个子类对应一个表外,其定义方式与Java对象的继承非常相似。也就是子类可以继承在父类中公共的属性定义,但在这里需要注意, 使用这种方式进行Java对象的映射配置必须要保证在超类中定义这些属性,在所有的表中所映射的字段的名字都必须相同。
如果采用另外一种方法来建立对象的映射文件,则可以弥补这方面的不足,但又会带来其他问题。在这种方法中,没有定义类之间的映射关系,而是用三个独立的class元素来描述这些对象的映射方法,如下所示:
<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CREDIT_AMOUNT"/> ... </class> <class name="CashPayment" table="CASH_PAYMENT"> <id name="id" type="long" column="CASH_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CASH_AMOUNT"/> ... </class> <class name="ChequePayment" table="CHEQUE_PAYMENT"> <id name="id" type="long" column="CHEQUE_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CHEQUE_AMOUNT"/> ... </class>
可以看到,采用这种独立映射方式的配置方法,在配置文件中没有定义这些类之间的任何关系。也就是说,三个类都是独立存在的。使用这种映射方式解决了相同属性必须使用相同字段名的限制,但又带来了另外一个问题,就是从父类继承的属性需要在每个子类中都进行相应的定义,造成属性配置的重复。
2.每个类分层结构一张表
采用这种方式的特点是需要为每个类分层建立一个表,也就是说依据继承层次的结构来确定建立表的数量。如下所示:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <property name="creditCardType" column="CCTYPE"/> ... </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
通过以上清单可以看出,这里的映射只需要一张表就可以完成所有实体对象的映射。这要求这个表必须具有与所有的映射对象的属性相对的字段,而在这里就存在一个很大的限制,也就是某个子类特有的属性所映射的字段不能设置非空属性,否则在进行其他子类的持久化的时候会产生异常。
采用这种映射方式需要注意的是它通过增加一个字段(在这里是 PAYMENT_TYPE 字段)来标识某个记录是属于哪个实体对象的。通过标签中的定义可以看出,如果该值为 CREDIT ,则表示这个记录是 CreditCardPayment 对象的持久化数据。如果该值为 CASH , 则表示该记录是 CashPayment 对象的持久化数据。
优点: 采用这种映射方式在执行对象检索的时候可以减少查询语句的执行次数。
3.每个子类一张表
和每个具体类一张表的映射策略区别在于每个具体类一张表的映射策略所建立的表是独立的,每个表都包括了子类所自定义的属性和由父类所继承的属性的映射字段。而采用每个子类一张表的映射策略时,子类所对应的表只包括所定义的属性,而子类所对应的表与父类所对应的表是通过外键来进行关联的。
使用这种映射策略的好处是父类所定义的属性就在父类的表中进行映射,而子类所定义的属性就在子类的表中进行映射。避免了子类所定义的表中仍然需要定义父类属性的映射 字段所带来的麻烦。如下示例:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="AMOUNT"/> ... <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> </class>
通过上面的配置文件可以看出,采用每个子类一张表的映射方式可以在最大程度上“模拟”面向对象的继承行为,通过一个外键字段将父表和子表进行关联。同时也避免了前面两种方式所带来的结构冗余的情况。
注意,对“每个子类一张表”的映射策略,Hibernate 的实现不需要辨别字段,而其他 的对象/关系映射工具使用了一种不同于 Hibernate 的实现方法,该方法要求在超类 表中有一个类型辨别字段(type discriminator column) 。Hibernate 采用的方法更 难实现,但从关系(数据库)的角度来看,按理说它更正确。若你愿意使用带有辨别字 段的“每个子类一张表”的策略,你可以结合使用 与,如下所示:
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> <join table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </join> </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> <join table="CHEQUE_PAYMENT" fetch="select"> <key column="PAYMENT_ID"/> ... </join> </subclass> </class>
可选的声明fetch="select" ,是用来告诉 Hibernate ,在查询超类时, 不要使用外部连接(outer join) 来抓取子类 ChequePayment 的数据。
4.对象继承的映射方法总结
三种继承映射方式的比较
比较方面 |
每个具体类一张表 |
每个类分层结构一张表 |
每个子类一张表 |
建立关系模型的原则 | 每个具体类对应 一张表,有多少具体类就需要建立多少个独立的表 | 描述一个继承关系只用一张表 | 每个子类使用一张表。但这些子类所对应的表都关联到基类所对应的表中 |
关系模型的优缺点 | 这种设计方式符合关系模型的设计原则,但有表中存在重复字段的问题 | 缺点有二:首先表中引入了区分子类的字段。其次,如果某个子类的某个属性的值不能为空,那么在数据库一级是不能设置该字段为NOT NULL的 | 这种设计方式完全符合关系模型的设计原则,而且不存在冗余 |
可维护性 | 如果需要对基类进行修改,则需要对基类以及该类的子类所对应的所有表都进行修改 | 维护起来比较方便,只需要修改一张表 | 维护起来比较方便,对每个类的修改只需要修改其所对应的表 |
灵活性 | 映射的灵活性很大,子类可以对包括基类属性在内的每一个属性进行单独的配置 | 灵活性差,表中的冗余字段会随着子类的增多而增加 | 灵活性很好,完全是参照对象继承的方式进行映射配置 |
查询的性能 | 对于子类的查询只需要访问单独的表,但对于父类的查询则需要检索所有的表 | 在任何情况下的查询都只需处理这一张表 | 对于父类的查询需要使用左外连接,而对于子类的查询则需要进行内连接 |
维护的性能 | 对于单个对象的持久化操作只需要处理一个表 | 对于单个对象的持久化操作只需处理一个表 | 对于子类的持久化操作至少需要处理两个表 |