现在主流的编程语言是面向对象的编程语言,而主流的数据库都是面向关系的数据库。对象与关系之间存在一定的不匹配性,比如说类存在继承,而数据库表却不存在这样子的做法,这篇文章是关于在进行Java类继承设计的时候,如何去设计相关的数据库表,以及如何配置Hibernate相关的关系映射文件的,所采用的Hibernate版本是3.x。在Hibernate中对于类继承问题提出的解决方案有3种:table per concrete class,table per subclass,table per class hierarchy。
table per concrete class
这种解决方案,就是每一个子类一张单独的表,并且把父类的属性也放到子类的表中去,从而就没有所谓的父类对应的表。如果有一个父类,两个子类,那么对应下来就是应该在数据库中创建两张表。举个例子:现在有一个父类商品Item,它现在派生出两个子类Book和DVD。Item的属性包括了id(Integer),name(String),price(Float),而子类Book新增属性pages(Integer),子类DVD新增属性zones(String)。那么在设计数据库时,就只需要创建两张表t_book和t_dvd,如下:
id | name | price | pages |
id | name | price | zones |
在这里,所使用的数据库是mysql,Java的Integer类型对应mysql的int类型,String对应mysql的varchar,Float对应mysql的float。
类的设计,表的设计都解决了,接下来就是要配置Hibernate关系映射文件,这种情况下不必去配置父类的关系映射文件,你有几个子类,就为几个子类配置关系映射文件就可以了。
Book.hbm.xml
<class name="com.daniel.model.persistence.Book" table="t_book">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"></generator>
</id>
<property name="name" column="name" type="java.lang.String"/>
<property name="price" column="price" type="java.lang.Float"/>
<property name="pages" column="pages" type="java.lang.Integer"/>
</class>
DVD.hbm.xml<class name="com.daniel.model.persistence.dvd" table="t_dvd">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"></generator>
</id>
<property name="name" column="name" type="java.lang.String"/>
<property name="price" column="price" type="java.lang.Float"/>
<property name="zones" column="zones" type="java.lang.String"/>
</class>
如果你使用HQL查询父类,那么Hibernate会发出若干条查询语句去查询子类(具体取决于该父类有几个子类)table per subclass
这种解决方案,是父类单独使用一张表,这张表记录着父类的属性,每一个子类又单独存在自己的一张表,如果有一个父类,两个子类,那么就存在三张表。此时,子类的表中要设置外键来记录其父类记录,也就是说,这种方案要比第一种方案多用一个列。还是上述的Item类,以及其两个派生类Book和DVD。数据库表的设计如下:
id | name | price |
book_id | pages |
dvd_id | zones |
在这里,book_id,dvd_id既是主键又是外键,这样可以保证父类主键值遇子类主键值一致(虽然说实际上是两个列,但是值一样)。在配置关系映射文件时,只需要配置一个关系映射文件,那就是Item.hbm.xml
<class name="com.daniel.model.persistence.Item" table="t_item">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"></generator>
</id>
<property name="name" column="name" type="java.lang.String"/>
<property name="price" column="price" type="java.lang.Float"/>
<!--配置子类Book-->
<joined-subclass name="com.daniel.model.persistence.Book" table="t_book">
<key column="book_id">
<propery name="pages" column="pages" type="java.lang.Integer"/>
</joined-subclass>
<!--配置子类DVD-->
<joined-subclass name="com.daniel.model.persistence.DVD" table="t_dvd">
<key column="dvd_id">
<propery name="zones" column="zones" type="java.lang.String"/>
</joined-subclass>
</class>
这种解决方案性能上不太被看好,原因是如果插入和删除一条记录,需要分别操作父类表和子类表各一次。即使是查询,也需要分别查询父类表和子类表。table per class hierarchy
这种解决方案,是把所有的父类属性和子类属性都写到一张表中,然后为了区分不同子类,需要在表中加入一个额外的列。同样的Item父类,两个子类Book和DVD,数据库表设计如下:
id | name | price | pages | zones | catalog |
其中catalog字段是用来区分不同子类的。
这种解决方案也是只需要配置一个关系映射文件,那就是Item.hbm.xml。
<class name="com.daniel.model.persistence.Item" table="t_item">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native"></generator>
</id>
<property name="name" column="name" type="java.lang.String"/>
<property name="price" column="price" type="java.lang.Float"/>
<!--配置区分子类字段-->
<discriminator column="catalog" type="string" />
<!--配置子类Book-->
<subclass name="com.daniel.model.persistence.Book" discriminator="1">
<propery name="pages" column="pages" type="java.lang.Integer"/>
</subclass>
<!--配置子类DVD-->
<joined-subclass name="com.daniel.model.persistence.DVD" discriminator-value="2">
<propery name="zones" column="zones" type="java.lang.String"/>
</joined-subclass>
</class>
这种解决方案中可以能会造成表的空间利用率较低,但是速度确实是非常快,因为所有的记录都在一张表中,不过时间与空间总是两难全的,具体使用哪种方案,得看你是偏重哪方面。