类之间的关系,我们可以分为四种:关联、依赖、继承、实现。而我们所说的实体类之间的关系往往会想到两种:关联和继承,其他两种为什么会不是很常用?首先类之间的依赖是一种比较弱的关系,在代码上可以理解为在一个类的方法的参数上或方法内部对另一个类有引用或调用,引用类或调用类不属于原始类的变量类型,实体类之间一般不存在方法,也就谈不上依赖了。实现描述的是类与接口的关系,一般接口用于定义方法,也就是相当于定义出一些规范,不进行实现。
在前面几篇文章中,我们了解和学习了如何使用Hibernate进行实体类之间的关联映射,可以说这样的关联关系是项目中经常用到的,在项目中我们还会时常考虑到实体类继承这样的情况,今天我们就说说怎么用Hibernate来实现实体类之间的继承映射。
原理分析
Hibernate提供了三种策略以实现类的继承映射:一棵继承树一张表、一个类一张表、一个具体子类一张表。
1.一棵继承树一张表
先来看第一种策略要达到的效果:
看我们的映射文件:
<hibernate-mapping package="com.tgb.hibernate">
<class name="Animal" lazy="false">
<id name="id">
<generator class="native" />
id>
<discriminator column="type" type="string" />
<property name="name" />
<property name="sex" />
<subclass name="Pig" discriminator-value="P">
<property name="weight" />
subclass>
<subclass name="Bird" discriminator-value="B">
<property name="height">property>
subclass>
class>
hibernate-mapping>
第一种方案采用subclass标签映射子类,采用discriminator鉴别器以区分子类。
2.一个类一张表
效果图如下:
映射文件如下:
<hibernate-mapping package="com.tgb.hibernate">
<class name="Animal" table="t_animal" lazy="false">
<id name="id">
<generator class="native" />
id>
<property name="name" />
<property name="sex" />
<joined-subclass name="Pig" table="t_pig">
<key column="pid" />
<property name="weight" />
joined-subclass>
<joined-subclass name="Bird" table="t_bird">
<key column="bid" />
<property name="height" />
joined-subclass>
class>
hibernate-mapping>
第二种方案使用joined-subclass标签来映射子类,每每看到join这个单词,就想到了SQL中的连接查询,此处亦有异曲同工之妙呀。
3.一个具体子类一张表
效果图如下:
映射文件如下:
<hibernate-mapping package="com.tgb.hibernate">
<class name="Animal" table="t_animal" abstract="true">
<id name="id">
<generator class="assigned" />
id>
<property name="name" />
<property name="sex" />
<union-subclass name="Pig" table="t_pig">
<property name="weight" />
union-subclass>
<union-subclass name="Bird" table="t_bird">
<property name="height" />
union-subclass>
class>
hibernate-mapping>
第三种方案使用union-subclass标签关联子类进行映射,该种方式将父类的属性联合添加到子类中,映射到表字段,也就是说,多个子类表中有相同的字段结构。
对比
三种实现策略中,第一种策略,表结构有冗余字段,不符合基本的三范式要求,但查询效率较高;第二种策略,存储清晰,子类对应表只存放扩展属性,但如果继承层次比较深,查询时关联的表也会很多;第三种策略不适合使用主键递增的策略方式。
三种方式有好有坏,只能借助经验和具体需求来选定使用哪一种策略。
操作示例
在了解了继承映射的实现原理之后,我们可以接触一个新的概念——多态查询:hibernate在加载数据的时候,能够采用instanceof能够鉴别出真正的类型。而Load在默认情况下是不支持多态查询的。
示例代码如下:
保存数据方法:
public void SaveAnimal(){
Session session = null;
Transaction tx = null;
try{
session = HibernateUtils.getSession();
tx = session.beginTransaction();
Pig pig = new Pig();
pig.setName("pig1");
pig.setSex(false);
pig.setWeight(200);
session.save(pig);
Bird bird = new Bird();
bird.setName("bird1");
bird.setSex(true);
bird.setHeight(100);
session.save(bird);
tx.commit();
}catch(Exception e){
e.printStackTrace();
if(tx != null){
tx.rollback();
}
}finally{
HibernateUtils.closeSession(session);
}
}
测试多态查询示例如下:
public void testLoad(){
Session session = null;
Transaction tx = null;
try{
session = HibernateUtils.getSession();
tx = session.beginTransaction();
Animal animal = (Animal)session.load(Animal.class, 1);
if(animal instanceof Pig){
System.out.println(animal.getName());
}
tx.commit();
}catch(Exception e){
e.printStackTrace();
if(tx != null){
tx.rollback();
}
}finally{
HibernateUtils.closeSession(session);
}
}
此方法在默认的配置下将不打印任何内容。
我们将映射文件Animal的class标签属性lazy设置为”false”,控制台将打印动物名。
总结
至此,Hibernate中的继承映射介绍完了,讲这么半天,其实是在做一件事,那就是如何将继承式的对象模型对应到数据库存储及其之后的读取、修改等操作。Hibernate为我们提供了很多方案,在实际项目中,按照具体情况(存储数据的数量级、继承层次、效率要求等),选取较为合适的策略。有了技术基础,就是经验、思想了。