首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 开源软件 >

Hibernate 承继

2012-07-27 
Hibernate 继承?在域模型中,类与类之间除了关联关系和聚集关系,还可以存在继承关系,在下图所示的域模型中,

Hibernate 继承

?

在域模型中,类与类之间除了关联关系和聚集关系,还可以存在继承关系,在下图所示的域模型中,Deparment类和Employee类之间为一对多的 双向关联关系,Employee类有两个子 类:Skiller类和Sales类。由于Java只允许一个类最多有一个直接的父类,因此Employee类、 Skiller类和Sales类构成了一棵继承关系树。
Hibernate 承继
?在 面向对象的范畴中,还存在多态的概念,多态建立在继承关系的基础上。简单地理解,多态是指当一个Java应用变量被声明为Employee类时,这个变量 实际上既可以引用Employee类自己的实例,Skiller类的实例,也可以引用Sales类的实例。Department类的getEmps()方 法通过Hibernate API从数据库中检索出所有Employee对象。getEmps()方法返回的集合既可以包含Employee类自己的实例,Skiller类的实例, 也可以引用Sales类的实例。,这种查询被称为多态查询。数据库表之间并不存在继承关系,那么如何把域模型的继承关系映射到关系数据模型中 呢?hibernate有以下三种映射方式:

继承关系树的根类对应一个表:对关系数据模型进行非常规设计,在数据库表中加入额外的区分子类型的字段。通过这种方式,可以使关系数据模型支持继承关系和多态。

继承关系树的每个类对应一个表(子类与父类通过外键关联):在关系数据模型中用外键参照关系来表示继承关系。

继承关系树的每个具体类对应一个表:关系数据模型完全不支持域模型中的继承关系和多态。

1.继承关系树的根类对应一个表employee(整个继承树一张表):

employee的表结构如下所示:

mysql> desc employee;
+------------+--------------+------+-----+---------+----------------+
| Field????? | Type???????? | Null | Key | Default | Extra????????? |
+------------+--------------+------+-----+---------+----------------+
| id???????? | int(11)????? | NO?? | PRI | NULL??? | auto_increment |
| type???????| int(11)????? | NO?? |???? | NULL??? |??????????????? |
| name?????? | varchar(255) | YES? | UNI | NULL??? |??????????????? |
| depart_id? | int(11)????? | YES? | MUL | NULL??? |??????????????? |
|?skill?????? | varchar(255) | YES? |???? | NULL??? |??????????????? |
|?saleAmount?| int(11)????? | YES? |???? | NULL??? |??????????????? |
+------------+--------------+------+-----+---------+----------------+

实体类Department和Employee请参看我前面的文章,Skiller和Sales分别如下所示:

Java代码?
  1. package?com.reiyen.hibernate.domain;??
  2. ??
  3. public?class?Skiller?extends?Employee?{??
  4. ??
  5. ????private?String?skill;??
  6. //setter和getter方法??
  7. }??
?Java代码?
  1. package?com.reiyen.hibernate.domain;??
  2. public?class?Sales?extends?Employee?{??
  3. ??
  4. ????private?int?saleAmount;??
  5. //setter和getter方法??
  6. }??

?Employee.hbm.xml映射文件如下:

Xml代码?
  1. <?xml?version="1.0"?>??
  2. <!DOCTYPE?hibernate-mapping?PUBLIC???
  3. ????"-//Hibernate/Hibernate?Mapping?DTD?3.0//EN"??
  4. ????"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">??
  5. <hibernate-mapping?package="com.reiyen.hibernate.domain">??
  6. ????<class?name="Employee"?discriminator-value="0">??
  7. ????????<id?name="id">??
  8. ????????????<generator?class="native"?/>??
  9. ????????</id>??
  10. ????????<!--discriminator(鉴别器):缺省类型为string,这里指定为int类型?-->??
  11. ????????<discriminator?column="type"?type="int"></discriminator>??
  12. ????????<property?name="name"?unique="true"/>??
  13. ????????<!--?name="department"?这个名称必须与Employee中的属性名一致.?设置了column="depart_id",默认它会去department中找id与depart_id值相等的对象.如果要找name的值与depart_id相等的对象,则可以设置property-ref="name"?-->??
  14. ????????<many-to-one?name="department"?column="depart_id"?/>??
  15. ????????<subclass?name="Skiller"?discriminator-value="1">??
  16. ????????????<property?name="skill"?/>??
  17. ????????</subclass>??
  18. ????????<subclass?name="Sales"?discriminator-value="2">??
  19. ????????????<property?name="saleAmount"?/>??
  20. ????????</subclass>??
  21. ????</class>??
  22. </hibernate-mapping>??
?

?测试类如下:

Java代码?
  1. public?class?Many2One?{??
  2. ??
  3. ????public?static?void?main(String[]?args)?{??
  4. ????????add();??
  5. ?????????query(1);//1??
  6. ????}??
  7. ??
  8. ????static?void?query(int?empId)?{??
  9. ????????Session?s?=?null;??
  10. ????????Transaction?tx?=?null;??
  11. ????????try?{??
  12. ????????????s?=?HibernateUtil.getSession();??
  13. ????????????tx?=?s.beginTransaction();??
  14. ????????????Employee?emp?=?(Employee)?s.get(Employee.class,?empId);//2??
  15. ????????????System.out.println(emp.getClass());??
  16. ????????????tx.commit();??
  17. ????????}?finally?{??
  18. ????????????if?(s?!=?null)??
  19. ????????????????s.close();??
  20. ????????}??
  21. ????}??
  22. ??
  23. ??
  24. ??
  25. ????static?void?add()?{??
  26. ????????Session?s?=?null;??
  27. ????????Transaction?tx?=?null;??
  28. ????????try?{??
  29. ????????????Department?depart?=?new?Department();??
  30. ????????????depart.setName("department?name");??
  31. ??????????????
  32. ????????????Employee?employee1?=?new?Employee();??
  33. ????????????employee1.setDepartment(depart);?//1?对象模型:建立两个对象的关联???
  34. ????????????employee1.setName("employee1?name1");??
  35. ??????????????
  36. ????????????Skiller?employee2?=?new?Skiller();??
  37. ????????????employee2.setDepartment(depart);?//2?对象模型:建立两个对象的关联???
  38. ????????????employee2.setName("employee2?name2");??
  39. ????????????employee2.setSkill("j2se");??
  40. ??????????????
  41. ????????????Sales?employee3?=?new?Sales();??
  42. ????????????employee3.setDepartment(depart);?//2?对象模型:建立两个对象的关联???
  43. ????????????employee3.setName("employee3?name3");??
  44. ????????????employee3.setSaleAmount(1000);??
  45. ??????????????
  46. ????????????s?=?HibernateUtil.getSession();??
  47. ????????????tx?=?s.beginTransaction();??
  48. ????????????s.save(depart);??
  49. ????????????s.save(employee1);??
  50. ????????????s.save(employee2);??
  51. ????????????s.save(employee3);??
  52. ????????????tx.commit();??
  53. ????????}?finally?{??
  54. ????????????if?(s?!=?null)??
  55. ????????????????s.close();??
  56. ????????}??
  57. ????}??
  58. }??

?程序运行后,控制台打印信息如下所示:

Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, depart_id, type) values (?, ?, 0)
Hibernate: insert into Employee (name, depart_id, skill, type) values (?, ?, ?, 1)
Hibernate: insert into Employee (name, depart_id, saleAmount, type) values (?, ?, ?, 2)
Hibernate: select employee0_.id as id1_0_, employee0_.name as name1_0_, employee0_.depart_id as depart4_1_0_, employee0_.skill as skill1_0_, employee0_.saleAmount as saleAmount1_0_, employee0_.type as type1_0_ from Employee employee0_ where?employee0_.id=??
class com.reiyen.hibernate.domain.Employee

employee表中记录如下所示:

mysql> select * from employee;
+----+------+-----------------+-----------+-------+------------+
| id | type | name??????????? | depart_id | skill | saleAmount |
+----+------+-----------------+-----------+-------+------------+
|? 1 |??? 0 | employee1 name1 |???????? 1 | NULL? |?????? NULL |
|? 2 |??? 1 | employee2 name2 |???????? 1 | j2se? |?????? NULL |
|? 3 |??? 2 | employee3 name3 |???? ? ? 1 | NULL? |?????? 1000 |
+----+------+-----------------+-----------+-------+------------+
3 rows in set (0.00 sec)

将测试代码中注释为1的语句改成:

Java代码?
  1. query(2);??

再运行,控制台打印的class如下所示(因为hibernate支持多态查询): class com.reiyen.hibernate.domain.Skiller

打印的查询语句还是如上面所示的没有改变。

在上面修改的基础上,再将测试代码中注释为2的语句改成:

Java代码?
  1. Employee?emp?=?(Employee)?s.get(Skiller.class,?empId);??

?再运行,则控制台打印的查询语句为:

Hibernate: select skiller0_.id as id1_0_, skiller0_.name as name1_0_, skiller0_.depart_id as depart4_1_0_, skiller0_.skill as skill1_0_ from Employee skiller0_ where?skiller0_.id=? and skiller0_.type=1?
class com.reiyen.hibernate.domain.Skiller

优点:操作效率高

缺点:如果说给employee增加子类的话,必须修改表结构,给表结构增加一个字段;同时表中对应子类的字段不能有非空约束.

2.继承关系树的每子类对应一个表(joined-subclass),表结构如下所示:


Hibernate 承继
?修改Employee.hbm.xml映射文件如下所示:

Xml代码?
  1. <?xml?version="1.0"?>??
  2. <!DOCTYPE?hibernate-mapping?PUBLIC???
  3. ????"-//Hibernate/Hibernate?Mapping?DTD?3.0//EN"??
  4. ????"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">??
  5. <hibernate-mapping?package="com.reiyen.hibernate.domain">??
  6. ????<class?name="Employee">??
  7. ????????<id?name="id">??
  8. ????????????<generator?class="native"?/>??
  9. ????????</id>??
  10. ????????<property?name="name"?unique="true"/>??
  11. ????????<!--?name="department"?这个名称必须与Employee中的属性名一致.?设置了column="depart_id",默认它会去department中找id与depart_id值相等的对象.如果要找name的值与depart_id相等的对象,则可以设置property-ref="name"?-->??
  12. ????????<many-to-one?name="department"?column="depart_id"?/>??
  13. ????????<joined-subclass?name="Skiller"?table="skiller">??
  14. ?????????<key?column="employee_id"?/>??
  15. ?????????<property?name="skill"?/>??
  16. ????????</joined-subclass>??
  17. ????????<joined-subclass?name="Sales"?table="sales">??
  18. ?????????<key?column="employee_id"?/>??
  19. ?????????<property?name="saleAmount"?column="sale_amount"?/>??
  20. ????????</joined-subclass>??
  21. ????</class>??
  22. </hibernate-mapping>??

?测试类不变,只是将测试代码中注释为1的语句改成:

Java代码?
  1. query(2);??

则控制台打印的信息如下所示:

Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, depart_id) values (?, ?)
Hibernate: insert into Employee (name, depart_id) values (?, ?)
Hibernate: insert into skiller (skill, employee_id) values (?, ?)?
Hibernate: insert into Employee (name, depart_id) values (?, ?)
Hibernate: insert into sales (sale_amount, employee_id) values (?, ?)
Hibernate: select employee0_.id as id1_0_, employee0_.name as name1_0_, employee0_.depart_id as depart3_1_0_, employee0_1_.skill as skill2_0_, employee0_2_.sale_amount as sale2_3_0_, case when employee0_1_.employee_id is not null then 1 when employee0_2_.employee_id is not null then 2 when employee0_.id is not null then 0 end as clazz_0_ from Employee employee0_?left outer join skiller?employee0_1_ on employee0_.id=employee0_1_.employee_id?left outer join sales?employee0_2_ on employee0_.id=employee0_2_.employee_id where employee0_.id=?
class com.reiyen.hibernate.domain.Skiller
?从打印的SQL语句可以看出,此时,如果保存的是Employee对象的子类的实例的话,则要在两张表中保存记录;如果查询的是子类对象的话,是三张表关联在一起进行查询。

?

在上面修改的基础上,再将测试代码中注释为2的语句改成:

Java代码?
  1. Employee?emp?=?(Employee)?s.get(Skiller.class,?empId);??

?再运行,则控制台打印的查询语句为:

Hibernate: select skiller0_.employee_id as id1_0_, skiller0_1_.name as name1_0_, skiller0_1_.depart_id as depart3_1_0_, skiller0_.skill as skill2_0_ from?skiller skiller0_ inner join Employee?skiller0_1_ on skiller0_.employee_id=skiller0_1_.id where skiller0_.employee_id=?
此时只关联两张表查询。

数据库中表记录如下所示:

mysql> select * from employee;
+----+-----------------+-----------+
| id | name??????????? | depart_id |
+----+-----------------+-----------+
|? 1 | employee1 name1 |???????? 1 |
|? 2 | employee2 name2 |???????? 1 |
|? 3 | employee3 name3 |???????? 1 |
+----+-----------------+-----------+
3 rows in set (0.00 sec)

mysql> select * from skiller;
+-------------+-------+
| employee_id | skill |
+-------------+-------+
|?????????? 2 | j2se? |
+-------------+-------+
1 row in set (0.00 sec)

mysql> select * from sales;
+-------------+-------------+
| employee_id | sale_amount |
+-------------+-------------+
|?????????? 3 |??????? 1000 |
+-------------+-------------+
1 row in set (0.00 sec)

?

3.混合使用,假设如果Sales的属性很多,而Skiller的属性很少,这时可以混使用“一个类继承体系一张表”和“每个子类一张表”,表结构如下所示:


Hibernate 承继
?Employee.hbm.xml映射文件如下所示:

Xml代码?
  1. <?xml?version="1.0"?>??
  2. <!DOCTYPE?hibernate-mapping?PUBLIC???
  3. ????"-//Hibernate/Hibernate?Mapping?DTD?3.0//EN"??
  4. ????"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">??
  5. <hibernate-mapping?package="com.reiyen.hibernate.domain">??
  6. ????<class?name="Employee"?discriminator-value="0">??
  7. ????????<id?name="id">??
  8. ????????????<generator?class="native"?/>??
  9. ????????</id>??
  10. ????????<discriminator?column="type"?type="int"?/>??
  11. ????????<property?name="name"?unique="true"?/>??
  12. ????????<!--?name="department"?这个名称必须与Employee中的属性名一致.?设置了column="depart_id",默认它会去department中找id与depart_id值相等的对象.如果要找name的值与depart_id相等的对象,则可以设置property-ref="name"?-->??
  13. ????????<many-to-one?name="department"?column="depart_id"?/>??
  14. <!--如果discriminator-value没有显式的给定值的话,则与name属性的值保持一致,即为Skiller?-->????????
  15. <subclass?name="Skiller"?discriminator-value="1">??
  16. ????????????<property?name="skill"?/>??
  17. ????????</subclass>??
  18. ????????<subclass?name="Sales"?discriminator-value="2">??
  19. ????????????<join?table="sales">??
  20. ????????????????<key?column="employee_id"?/>??
  21. ????????????????<property?name="saleAmount"?column="sale_amount"?/>??
  22. ????????????</join>??
  23. ????????</subclass>??
  24. ????</class>??
  25. </hibernate-mapping>??

?此时测试类不变,只是将测试代码中注释为1的语句改成:

Java代码?
  1. query(2);??

然后在上面的基础上运行原程序,则控制台会打印出如下异常信息:

Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, depart_id, type) values (?, ?, 0)
Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not insert: [com.reiyen.hibernate.domain.Employee]

Caused by: com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: Unknown column 'type' in 'field list'

这是因为我在hibernate.cfg.xml配置文件中配置了此项:

Xml代码?
  1. <property?name="hbm2ddl.auto">create</property>??

?所以在此次程序运行时,会删除数据库中的employee表,sales表,而employee表中有skiller表的外键关联,所以不能删除employee数据表了,所以抛出了上面的异常。此时你再查看数据库表,如下所示 :

mysql> select * from employee;
+----+-----------------+-----------+
| id | name??????????? | depart_id |
+----+-----------------+-----------+
|? 1 | employee1 name1 |???????? 1 |
|? 2 | employee2 name2 |???????? 1 |
|? 3 | employee3 name3 |???????? 1 |
+----+-----------------+-----------+
3 rows in set (0.00 sec)

mysql> select * from skiller;
+-------------+-------+
| employee_id | skill |
+-------------+-------+
|?????????? 2 | j2se? |
+-------------+-------+
1 row in set (0.00 sec)

mysql> select * from sales;
Empty set (0.00 sec)

所以得先手动删除skiller数据表,然后再来运行程序:

?

如果Employee.hbm.xml配置文件中

Xml代码?
  1. <subclass?name="Skiller"?>??

不配置discriminator-value="1",则会抛出如下异常:

java.lang.ExceptionInInitializerError

Caused by: org.hibernate.MappingException: Could not format discriminator value to SQL string

因为如果discriminator-value没有显式的给定值的话,则与name属性的值保持一致,即为Skiller ,所以会抛出如上异常!

?

4.继承关系树的每个具体类对应一个表(union-subclass)

表结构如下所示:


Hibernate 承继

Employee.hbm.xml映射文件如下:

Xml代码?
  1. <?xml?version="1.0"?>??
  2. <!DOCTYPE?hibernate-mapping?PUBLIC???
  3. ????"-//Hibernate/Hibernate?Mapping?DTD?3.0//EN"??
  4. ????"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">??
  5. <hibernate-mapping?package="com.reiyen.hibernate.domain">??
  6. ????<class?name="Employee">??
  7. ????????<id?name="id">??
  8. ????????????<generator?class="hilo"?/>??
  9. ????????</id>??
  10. ????????<property?name="name"?unique="true"?/>??
  11. ????????<many-to-one?name="department"?column="depart_id"?/>??
  12. ????????<union-subclass?name="Skiller"?table="skiller">??
  13. ?????????<property?name="skill"?/>??
  14. ????????</union-subclass>??
  15. ????????<union-subclass?name="Sales"?table="sales">??
  16. ?????????<property?name="saleAmount"?column="sale_amount"?/>??
  17. ????????</union-subclass>??
  18. ????</class>??
  19. </hibernate-mapping>??

?此时主键增长不能再是:

Xml代码?
  1. <generator?class="native"?/>??

因为如果使用native的话三张表会产生相同的id值,这样当根据id查询Employee时就会出错了。所以如果你配置成native时会抛出如下异常(因为Employee实体类中id对应 的是int了,所以在此使用hilo(高低位)主键生成方式):

org.hibernate.MappingException:?Cannot use identity column key generation with <union-subclass> mapping for: com.reiyen.hibernate.domain.Skiller

?

运行测试程序后,此时控制台打印信息如下所示:

Hibernate: insert into Department (name) values (?)
Hibernate: insert into Employee (name, depart_id, id) values (?, ?, ?)
Hibernate: insert into skiller (name, depart_id, skill, id) values (?, ?, ?, ?)
Hibernate: insert into sales (name, depart_id, sale_amount, id) values (?, ?, ?, ?)
Hibernate: select employee0_.id as id1_0_, employee0_.name as name1_0_, employee0_.depart_id as depart3_1_0_, employee0_.skill as skill2_0_, employee0_.sale_amount as sale1_3_0_, employee0_.clazz_ as clazz_0_ from ( select id, null as sale_amount, depart_id, null as skill, name, 0 as clazz_ from Employee?unionselect id, null as sale_amount, depart_id, skill, name, 1 as clazz_ from skiller?union?select id, sale_amount, depart_id, null as skill, name, 2 as clazz_ from sales ) employee0_ where?employee0_.id=??
class com.reiyen.hibernate.domain.Skiller

执行查询时,首先使用子查询,在子查询中使用union将三张表的结果全成一张表,然后再在合成的结果集中进行查询。

如果Employee是一个抽象类,?你不想?在数据表中对应相应的数据表,则可以设置abstract="true"?.如下所示:

Xml代码?
  1. <class?name="Employee"?abstract="true"?>??

??此外,如果继承关系中有接口,可以把它当作抽象类对待。

三种映射方式的比较和选择
为了方便说明为三种方式按顺序标号为[1]整个继承树一张表;[2]每子类对应一个表(joined-subclass);[4]每个具体类对应一个表(union-subclass)。
1、复杂度:

??? [1]简单;
??? [2]表较多且之间有外键约束;

??? [4]包含重复字段;
2、查询性能:

??? [1]效率高;
??? [2]需要表内连接或左外连接;

??? [4]若查询父类需查所有子类表;
3、可维护性:

??? [1]只需修改一个表;
??? [2]若某个类属性变化只修改这个类对应的表;

??? [4]若父类属性变化需要修改所有子类对应的表;
综上,选择时,可以参考以下原则:
1、子类属性不是非常多时,优先考虑[1],因为其性能最佳。
2、子类属性非常多,且对性能要求不是很严格时,优先考虑[2]

?

热点排行