简介说明:
在域模型中,类与类之间除了关联关系和聚集关系,还可以存在继承关系,在如图所示的域模型中,Company类和Employee类之间为一对多的双向关联关系(假定不允许雇员同时在多个公司兼职),Employee类为抽象类,因此它不能被实例化,它有两个具体的子类:HourlyEmployee类和SalaiedEmployee类。由于Java只允许一个类最多有一个直接的父类,因此Employee类、HourlyEmployee类和SalariedEmployee类构成了一棵继承关系树。
在面向对象的范畴中,还存在多态的概念,多态建立在继承关系的基础上。简单地理解,多态是指当一个Java应用变量被声明为Employee类时,这个变量实际上既可以引用HourlyEmployee类的实例,也可以引用SalariedEmployee类的实例。以下这段程序代码就体现了多态:
List employees=businessService.findAllEmployees();
Iterator it=employees.iterator();
while(it.hasNext()){
Employee e=(Employee)it.next();
if(e instanceof HourlyEmployee){
System.out.println(e.getName()+" "+((HourlyEmployee)e).getRate());
}else{
System.out.println(e.getName()+" "+((SalariedEmployee)e).getRate());
}
}
BusinessService类的findAllEmployees()方法通过Hibernate API从数据库中检索出所有Employee对象。findAllEmployees()方法返回的集合既包含HourlyEmployee类的实例,也包含SalariedEmployee类的实例,这种查询被称为多态查询。以上程序中变量e被声明为Employee类型,它实际上既可能引用HourlyEmployee类的实例,也可
能引用SalariedEmployee类的实例。
能引用SalariedEmployee类的实例。
此外,从Company类到Employee类为多态关联,因为Company类的employees集合中可以包含HourlyEmployee类和SalariedEmployee类的实例。从Employee类到Company类不是多态关联,因为Employee类的company属性只会引用Company类本身的实例。
数据库表之间不存在继承关系,那么如何把域模型的继承关系映射到关系数据模型中,有以下三种方式:
- 继承关系树的每个具体类对应一个表: 关系数据模型完全不支持域模型中的继承关系和多态。
- 继承关系树的根类对应一个表:对关系数据模型进行非常规设计,在数据库表中加入额外的区分子类型的字段。通过这种方式,可以使关系数据模型支持继承关系和多态。
- 继承关系树的每个类对应一个表:在关系数据模型中用外键参照关系来表示继承关系。
具体类是指非抽象的类,具体类可以被实例化。HourlyEmpolyee和SalariedEmployee类就是具体类。
以上三种方式都有利也有弊。
继承关系树的每个具体类对应一个表
把每个具体类映射到一张表是最简单的映射方式。如图12-2所示,在关系数据模型中只需定义COMPANIES、HOURLY_EMPLOYEES和SALARIED_EMPLOYEES表。为了叙述的方便,下文把HOURLY_EMPLOYEES表简称为HE表,把SALARIED_EMPLOYEES表简称为SE表。HourlyEmployee类和HHE表对应HourlyEmployee类本身的rate属性,以及从Employee类中继承的id属性和name属性,在HE表中都有对应的字段。此外,HourlyEmployee类继承了Employee类与Company类的关联关系,与此对应,在HE表中定义了参照COMPANIES表的COMPANY_ID外键。
SalariedEmployee类和SE表对应,SalariedEmployee类本身的salary属性,以及从Employee类中继承的id属性和name属性,在SE表中都有对应的字段。此外SalariedEmployee类继承了Employee类与Company类的关联关系,与此对应,在SE表中定义了参照COMPANIES表的COMPANY_ID外键。
Company类、HourlyEmployee类和SalariedEmployee类都有相应的映射文件,而Employee类没有相应的映射文件。如下图显示了持久化类、映射文件和数据库表之间的对应关系。
SalariedEmployee类和SE表对应,SalariedEmployee类本身的salary属性,以及从Employee类中继承的id属性和name属性,在SE表中都有对应的字段。此外SalariedEmployee类继承了Employee类与Company类的关联关系,与此对应,在SE表中定义了参照COMPANIES表的COMPANY_ID外键。
Company类、HourlyEmployee类和SalariedEmployee类都有相应的映射文件,而Employee类没有相应的映射文件。如下图显示了持久化类、映射文件和数据库表之间的对应关系。
图12-2 每个具体类对应一个表
图12-3 持久化类、映射文件和数据表之间的对应关系
如果Empolyee类不是抽象类,即Employee类本身也能被实例化,那么还需要为Empolyee类创建对应的EMPLOYEES表,此时HE表和SE表的结构仍然和图12-2中所示的一样。这意味着在EMPLOYEES表、HE表和SE表中都定义了相同的NAME字段及参照COMPANYS表的外键COMPANY_ID。另外,还需为EMPLOYEE类创建单独的Employee.hbm.xml文件。
创建映射文件
从Company类到Employee类是多态关联,但是由于关系数据模型没有描述Employee类和它的两个子类的继承关系,因此无法映射Company类的employees集合。例程12-1是Company.hbm.xml文件的代码,该文件仅映射了Company类的id和name属性。
例程12-1 Company.hbm.xml
<hibernate-mapping>
<class name="mypack.Company" table="COMPANIES">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<property name="name" type="string" column="NAME"/>
</class>
</hibernate-mapping>
HourlyEmployee.hbm.xml文件用于把HourlyEmployee类映射到HE表,在这个映射文件中,除了需要映射HourlyEmployee类本身的rate属性,还需要映射从Employee类中继承的name属性,此外还要映射从Employee类中继承的与Company类的关联关系。例程12-2是HourlyEmployee.hbm.xml文件的代码。
例程 12-2 HourlyEmployee.hbm.xml
<hibernate-mapping>
<class name="mypack.HourlyEmployee" table="HOURLY_EMPLOYEES">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<property name="name" type="string" column="NAME"/>
<property name="rate" type="double" column="RATE"/>
<many-to-one name="company" column="COMPANY_ID" class="mypack.Company"/>
</class>
</hibernate-mapping>
SalariedEmployee.hbm.xml文件用于把SalariedEmployee类映射到SE表,在这个映射文件中,除了需要映射SalariedEmployee类本身的salary属性,还需要映射从Employee类中继承的name属性,此外还要映射从Employee类中继承的与Company类的关联关系。例程12-3是SalariedEmployee.hbm.xml文件的代码。
例程12-3 SalariedEmployee.hbm.xml
<hibernate-mapping>
<class name="mypack.SalariedEmployee" table="SALARIED_EMPLOYEES">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<property name="name" type="string" column="NAME"/>
<property name="salary" type="double" column="SALARY"/>
<many-to-one name="company" column="COMPANY_ID" class="mypack.Company"/>
</class>
</hibernate-mapping>
操作持久化对象
这种映射方式不支持多态查询。对于以下查询语句。
List employees=session.createQuery("from Employee").list();
如果Employee类是抽象类,那么Hibernate会抛出异常。如果Employee类是具体类,那么Hibernate仅查询EMPLOYEES表,检索出Employee类本身的实例,但不会检索出它的两个子类的实例。
- findAlIEmployees():检索数据库中所有的Employee对象。
- loadCompany():加载一个Company对象。
- saveEmployee():保存一个Employee对象。
List results=new ArrayList();
tx=session.beginTransaction();
List hourlyEmployees=session.createQuery(" from HourlyEmployee").list();
results.addAll(hourlyEmployees);
List salariedEmployees=session.createQuery(" from SalariedEmployee").list();
results.addAll(salariedEmployees);
tx.commit();
return results;
为了检索所有的Employee对象,必须分别检索所有的HourlyEmployee实例和SalariedEmployee实例,然局把它们合并到同一个集合中。在运行第一个Query的list()方法时,Hibernate执行以下select语句:
select * from HOURLY_EMPLOYEES;
在运行第二个Query的list()方法时 Hibernate会执行以下的select语句
select * from SALARIED_EMPLOYEES;
(2) 运行loadCompany() 方法,它的代码如下:
tx=session.beginTransaction();
Company company=(Company)session.get(Company.class,new Long(id));
List hourlyEmployees=session.createQuery(" from HourlyEmployee h where h.company.id="+id).list();
company.getEmployees().addAll(hourlyEmployees);
List salariedEmployees=session.createQuery(" from SalariedEmployee s where s.company.id="+id).list();
company.getEmployees().addAll(salariedEmployees);
tx.commit();
return company;
(3) 运行saveEmployee(Employee employee)方法,它的代码如下:
tx=session.beginTransaction();
session.save(employee);
tx.commit();
在test()方法中,创建了一个HourlyEmployee实例,然后调用saveEmployee()方法保存这个实例:
Employee employee=newHourlyEmployee("Mary",300,company);
saveEmployee(employee);
Session的save()方法能判断employee变量实际引用的实例的类型,如果employee变量引用HourlyEmployee实例,就向HE表中插入一条记录,执行如下insert语句:
insert into HOURLY_EMPLOYEES(ID,NAME,RATE,CUSTOMER_ID) values(3, 'Mary', 300,1);
如果employee变量引用SalariedEmployee实例,就向SE表插入一条记录。
继承关系树的根类对应一个表
这种映射方式只需为继承关系树的Employee根类创建一张表EMPLOYEES如图12-4所示,在EMPLOYEES表中不仅提供和Employee类的属性对应的字段,还要提供和它的两个子类的所有属型对应的字段,此外,EMPLOYEES表中需要额外加入一个字符串类型的EMPLOYEE_TYPE字段,用于区分Employee的具体类型。
Company类和Employee类有相应的映射文件,而HourlyEmployee类和SalariedEmployee类没有相应的映射文件。图12-5显示了持久化类、映射文件和数据库表之间的对应关系。
创建映射文件
从Company类到Employee类是多态关联,由于关系数据模型可以反映出Employee类和它的两个子类的继承关系,因此可以映射Company类的employees集合。例程12-5是Company.hbm.xml文件的代码,该文件不仅映射了Company类的id和name属性,还映射了它的employees集合。
例程12-5 Company.hbm.xml
<hibernate-mapping>
<class name="mypack.Company" table="COMPANIES">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<property name="name" type="string" column="NAME" />
<set name="employees" inverse="true">
<key column="COMPANY_ID" />
<one-to-many class="mypack.Employee" />
</set>
</class>
</hibernate-mapping>
Employee.hbm.xml文件用于把Employee类映射到EMPLOYEES表,在这个映射文件中,除了需要映射Employee类本身的属性,还需要在<subclass>元素中映射两个子类的属性。例程12-6是Employee.hbm.xml文件的代码:
例程 12-6 Employee.hbm.xml
<hibernate-mapping>
<class name="mypack.Employee" table="EMPLOYEES">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<discriminator column="EMPLOYEE_TYPE" type="string" />
<property name="name" type="string" column="NAME" />
<many-to-one name="company" column="COMPANY_ID" class="mypack.Company" />
<subclass name="mypack.HourlyEmployee" discriminator-value="HE">
<property name="rate" type="double" column="RATE" />
</subclass>
<subclass name="mypack.SalariedEmployee" discriminator-value="SE">
<property name="salary" type="double" column="SALARY" />
</subclass>
</class>
</hibernate-mapping>
在Employee.hbm.xml文件中,<discriminator>元素指定EMPLOYEES表中用于区 分Employee类型的字段为EMPLOYEE_TYPE,两个<subclass>元素用于映射 HourlyEmployee类和SalariedEmployee类,<subclass>元素的discriminator-value属性指 定EMPLOYEE TYPE字段的取值。EMPLOYEES表中有以下记录:
其中ID为1和2的记录的EMPLOYEE_TYPE字段的取值为“HE”,因此它们对应HourlyEmployee类的实例,其中ID为3和4的记录的EMPLOYEE_TYPE字段的取值为“SE”,因此它们对应SalariedEmployee类的实例这种映射方式要求EMPLOYEES表中和子类属性对应的字段允许为null,如ID为1和2的记录的SALARY字段为null,而ID为3和4的记录的RATE字段为null 如果业务需求规定SalariedEmployee对象的rate属性不允许为null,显然无法在EMPLOYEES表中为SALARY字段定义not null约束,可见这种映射方式无法保证关系数据模型的数据完整性。
如果Employee类不是抽象类,即它本身也能被实例化,那么可以在<class>元素中定义它的discriminator值,形式如下:
如果Employee类不是抽象类,即它本身也能被实例化,那么可以在<class>元素中定义它的discriminator值,形式如下:
<class name="mypack.Employee" table="EMPLOYEES" discriminator-value="EE">
以上代码表明,如果EMPLOYEES表中一条记录的EMPLOYEE_TYPE字段的取值为"EE",那么它对应Employee类本身的实例。
操作持久化对象
这种映射方式支持多态查询,对于以下查询语句:
List employees=session.createQuery("from Employee").list();
Hibemate会检索出所有的HourlyEmployee对象和SalariedEmployee对象。此外也可以单独查询Employee类的两个子类的实例,例如:
Hibemate会检索出所有的HourlyEmployee对象和SalariedEmployee对象。此外也可以单独查询Employee类的两个子类的实例,例如:
List hourlyEmployees=session.createQuery("from HourlyEmployee").list();
业务代码类中有以下方法:
- findAllHourlyEmployees():检查数据库中所有的HourlyEmployee对象。
- findAlIEmployees():检索数据库中所有的Employee对象。
- loadCompany():加载一个Company对象。
- saveEmployee():保存一个Employee对象。
(1) 运行findAllHourlyEmployees() 方法,它的代码如下:
tx=session.beginTransaction();
List results=session.createQuery("from HourlyEmployee").list();
tx.commit();
return results;
在运行Query的list()方法时,Hibernate执行以下的select语句:
select * from EMPLOYEES where EMPLOYEE_TYPE='HE';
(2) 运行findAllEmployees()方法,它的代码如下:
tx=session.beginTransaction();
List results=session.createQuery("from Employee").list();
tx.commit();
return results;
在运行Query的list()方法时,Hibernate执行以下的select语句:
select * from EMPLOYEES ;
在这种映射方式下,Hibernate支持多态查询,对于从EMPLOYEES表获得的查询结果,如果EMPLOYEE_TYPE字段取值为“HE”,就创建HoulyEmployee实例,如果EMPLOYEE_TYPE字段取值为“SE”,就创建SalariedEmployee实例。
(3)运行loadCompany()方法,它的代码如下:
tx=session.beginTransaction();
Company company=(Company)session.get(Company.class,new Long(id));
Hibernate.initialize(company.getEmployees());
tx.commit();
这种映射方式支持多态关联。如果在Company.hbm.xml文件中对employees集合设置了立即检索策略,那么Session的get()方法加载的Company对象的employees集合中包含所有关联的Employee对象。由于提供的Company.hbm.xml文件对employees集合采用默认的延迟检索策略,因此以上程序代码通过Hibemate类的静态
initialize()方法来显式初始化employees集合。
initialize()方法来显式初始化employees集合。
(4)运行saveEmployee(Employee employee)方法,它的代码如下:
tx=session.beginTransaction();
session.save(employee);
tx.commit();
session.save(employee);
tx.commit();
在test方法中,创建了一个HourlyEmployee实例,然后调用saveEmployee()方法保存这个实例:
Employee employee=new HourlyEmployee("Mary",300,company);
saveEmployee(employee);
Session的save()方法能判断employee变量实际引用的实例的类型,如果employee变量引用HourlyEmployee实例,就执行如下insert语句:
Session的save()方法能判断employee变量实际引用的实例的类型,如果employee变量引用HourlyEmployee实例,就执行如下insert语句:
insert into EMPLOYEES(ID,NAME,RATE, EMPLOYEE_TYPE, CUSTOMER_ID) values(5, 'Mary', 300, 'HE',1);
以上insert语句没有为SalariedEmployee类的salary属性对应的SALARY字段赋值,因此这条记录的SALARY字段为null.
以上insert语句没有为SalariedEmployee类的salary属性对应的SALARY字段赋值,因此这条记录的SALARY字段为null.
继承关系树的每个类对应一个表
在这种映射方式下,继承关系树的每个类及接口都对应一个表。在本例中需要创建EMPLOYEES、HE和SE表。如图12-6所示,EMPLOYEES表仅包含和Employee类的属性对应的字段,HE表仅包含和HourlyEmployee类的属性对应的
字段,SE表仅包含和SalariedEmployee类的属性对应的字段。此外,HE表和SE表都以EMPLOYEE_ID字段作为主键,该字段还同时作为外键参照EMPLOYEES表。
字段,SE表仅包含和SalariedEmployee类的属性对应的字段。此外,HE表和SE表都以EMPLOYEE_ID字段作为主键,该字段还同时作为外键参照EMPLOYEES表。
Company类和Employee类有相应的映射文件,而HourlyEmployee类和SalariedEmployee类没有相应的映射文件。图12-7显示了持久化类、映射文件和数据库表之间的对应关系。
创建映射文件
从Company类到Employee类是多态关联,由于关系数据模型反映出了Employee类和它的两个子类的继承关系,因此可以映射Company类的employees集合。例程12-7是Company.hbm.xml文件的代码,该文件不仅映射了Company类的id和name属性,还映射了它的employees集合。
<hibernate-mapping>
<class name="mypack.Company" table="COMPANIES">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<property name="name" type="string" column="NAME" />
<set name="employees" inverse="true">
<key column="COMPANY_ID" />
<one-to-many class="mypack.Employee" />
</set>
</class>
</hibernate-mapping>
Employee.hbm.xml文件用于把Employee类映射到EMPLOYEES表,在这个映射文件中,除了需要映射Employee类本身的属性,还需要在<joined-subclass>元素中映射两个子类的属性。例程12-8是Employee.hbm.xml文件的代码。
例程 12-8 Employee.hbm.xml
<hibernate-mapping>
<class name="mypack.Employee" table="EMPLOYEES">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<property name="name" type="string" column="NAME" />
<many-to-one name="company" column="COMPANY_ID" class="mypack.Company" />
<joined-subclass name="mypack.HourlyEmployee" table="HOURLY_EMPLOYEES">
<key column="EMPLOYEES_ID" />
<property name="rate" type="double" column="RATE" />
</joined-subclass>
<joined-subclass name="mypack.SalariedEmployee" table="SALARIED_EMPLOYEES">
<key column="EMPLOYEES_ID" />
<property name="salary" type="double" column="SALARY" />
</joined-subclass>
</class>
</hibernate-mapping>
在Employee.hbm.xml文件中,两个<joined-subclass>元素用于映射HourlyEmployee类和SalariedEmployee类,<joined-subclass>元素的<key>子元素指定HE表和SE表中即作为主键,又作为外键的EMPLOYEE_ID字段。图12-8显示了EMPLOYEES表、HE表和SE表中记录的参照关系。
也可以在单独的映射文件中配置<subclass>或<joined-subclass>元素,但此时必须显式设定它们的extends属性。例如,可以在单独的HourlyEmployee.hbm.xml文件中映射HourlyEmployee类:
<hibernate-mapping>
<joined-subclass name="mypack.HourlyEmployee" table="HOURLY_EMPLOYEES" extends="mypack.Employee">
......
</joined-subclass>
</hibernate-mapping>
使用注意事项:
值得注意的是,在<subclass>元素中只能嵌入<subclass>子元素,但不能嵌入<joined-subclass>子元素,而在<joined-subclass>元素中只能嵌入<joined-subclass>子元素,但不能嵌入<subclass>子元素。
操纵持久化对象
这种映射方式支持多态查询,对于以下查询语句:
List employees=session.createQuery("from Employee").list();
Hibernate会检索出所有的HourlyEmployee对象和SalariedEmployee对象。此外,也可以单独查询Employee类的两个子类 的实例,例如:
List hourlyEmployees=session.createQuery("from HourlyEmployee").list();
业务代码类中有以下方法:
- findAllHourlyEmployees():检查数据库中所有的HourlyEmployee对象。
- findAlIEmployees():检索数据库中所有的Employee对象。
- loadCompany():加载一个Company对象。
- saveEmployee():保存一个Employee对象。
(1) 运行findAllHourlyEmployees()非,它的代码如下:
tx = session.beginTransaction();
List results=session.createQuery("from HourlyEmployee").list();
tx.commit();
return results;
在运行Query的list()方法时,Hibernate执行以下的Select语句:
select * from HOURLY_EMPLOYEES he inner join EMPLOYEES e on he.EMPLOYEE_ID=e.ID;
Hibernate通过HE表与EMPLOYEES表的内连接获得HourlyEmployee对象的所有属性值。
(2) 运行findAllEmployees() 方法,它的代码如下:
tx=session.beginTransaction();
List results=session.createQuery("from Employee").list();
tx.commit();
return results;
在运行Query的list()方法时,Hibernate执行以下Select语句:
select * from EMPLOYEES e
left outer join HOURLY_EMPLOYEES he on e.ID = he.EMPLOYEE_ID
left outer join SALARIED_EMPLOYEES se on e.ID = se.EMPLOYEE_ID;
Hibernate把EMPLOYEES表与HE表及SE表进行左外连接,从而获得HourlyEmployee对象和SalariedEmployee对象的所有属性。在这种映射方式下,Hibernate支持多态查询,对于以上查询语句获得的查询结果,如果HE表的EMPLOYEE_ID字典不为null,就创建HourlyEmployee实例;如果SE表的EMPLOYEE_ID不为null,就创建SalariedEmployee实例。
(3) 运行loadCompany()方法,它的代码如下:
tx=session.beginTransaction();
Company company=(company)session.get(Company.class, new Long(id));
Hibernate.initialize(company.getEmployees());
tx.commit();
这种映射方式支持多态查询。如果在Company.hbm.xml文件中对employees集合设置了立即检索策略,那么Session的 get()方法加载的Company对象的employees集合中包含了所有关联的Employees对象。由于以上Company.hbm.xml文件对employees集合使用了默认的延迟检索的策略,因此以上程序代码还通过Hibernate累的静态initialize()方法来显式初始化employees集合,初始化时执行以下的select语句:
select e.COMPANY_ID, e.ID, e.NAME, he.RATE, se.SALARY, case when he.EMPLOYEE_ID is not null then 1 when se.EMPLOYEE_ID is not null then 2 when e.ID is not null then 0 as clazz from EMPLOYEES e left outer join HOURLY_EMPLOYEES he on e.ID =he.EMPLOYEE_ID left outer join SALARIED_EMPLOYEES se on e.ID=se.EMPLOYEE_ID where e.COMPANY_ID=1;
(4)运行saveEmployee(Employee employee)方法,它的代码如下:
tx=session.beginTransaction();
session.save(employee);
tx.commit();
在Test方法中,创建了一个HourlyEmployee实例,然后调用saveEmployee()方法保存这个实例:
Employee employee=new HourlyEmployee("Mary", 300, company);
saveEmployee(employee);
Session的save()方法能判断employee变量实际引用的实例的类型,如果employee变量引用HourlyEmployee实例,就执行如下的insert语句:
insert into EMPLOYEES (ID, NAME, COMPANY_ID) values (5, 'Mary', 1);
insert into HOURLY_EMPLOYEES (EMPLOYEE_ID, RATE) values (5, 300);
可见,每保存一个HourlyEmployee对象,需要分别向EMPLOYEES表和HE表插入一条记录,EMPLOYEES表的记录和HE表的记录共享一个主键。
选择继承的映射方式
比较三种映射方式
如果不需要支持多态查询和多态关联,可以采用每个具体类对应一个表的映射方式;如果需要支持多态查询和多态关联,并且子类包含的属性不多,可以采用根类对应一个表的映射方式;如果需要支持多态查询和多态关联,并且子类包含的属性很多,可以采用每个类对应一个表的映射方式。如果继承关系树中包含接口,可以将它当做抽象类来处理。
图12-9显示了一棵复杂的继承关系树,其中DOClass为抽象类,其它均为具体类。
可以将图12-9的继承关系树分解成3棵子树:
- DOClass类、ClassA类和ClassB类为一棵子树:DOClass类为抽象类,位于整个继承关系树的顶层,通常不会对它进行多态查询,因此可以采用每个具体类对应一个表的映射方式,ClassA类对应TABLE_A表,ClassB类对应TABLEB表。
- ClassA类、ClassC类、ClassD类、ClassG类和ClassH类为一棵子树:ClassA类的所有子类都只包含少量属性,因此可以采用根类对应一个表的映射方式,ClassA类对应TABLEA表
- ClassB类、ClassE类和ClassF为一棵子树:ClassB类的两个子类都包含很多属性,因此采用每个类对应一个表的映射方式,ClassB类对应TABLE_B表,ClassE类对应TABLE_E表,ClassF类对应TABLE_F表。
如图12-10所示,在关系数据模型中,只需创建TABLE_A、TABLE_B、TABLE_E和TABLE_F表,其中TABLE_A中包含了与DOClass、ClassA、ClassC、ClassD、ClassG和ClassH的属性对应的字段。TABLE_B中包含了与DOClass和ClassB的属性对应的字段,TABLE_E和TABLE_F的BID字段既是主键,又是参照TABLE_B表的外键.
示例如下:
只需要创建两个映射文件,ClassA.hbm.xml和ClassB.hbm.xml如下所示:
例程12-9 ClassA.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.test.hibernate3.demo1.ClassA" table="TABLE_A"
discriminator-value="A">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<discriminator column="A_TYPE" type="string" />
<property name="a1" type="string" column="A1" />
<subclass name="cn.test.hibernate3.demo1.ClassC"
discriminator-value="C">
<property name="c1" column="C1" type="string" />
</subclass>
<subclass name="cn.test.hibernate3.demo1.ClassD"
discriminator-value="D">
<property name="d1" column="D1" type="string" />
<subclass name="cn.test.hibernate3.demo1.ClassG"
discriminator-value="G">
<property name="g1" column="G1" type="string" />
</subclass>
<subclass name="cn.test.hibernate3.demo1.ClassH"
discriminator-value="H">
<property name="h1" column="H1" type="string" />
</subclass>
</subclass>
</class>
</hibernate-mapping>
例程 12-10 ClassB.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.test.hibernate3.demo1.ClassB" table="TABLE_B">
<id name="id" type="long" column="ID">
<generator class="increment" />
</id>
<property name="b1" type="string" column="B1" />
<joined-subclass name="cn.test.hibernate3.demo1.ClassE" table="TABLE_E">
<key column="B_ID" />
<property name="e1" column="E1" type="string" />
<property name="e2" column="E2" type="string" />
<property name="e3" column="E3" type="string" />
<property name="e4" column="E4" type="string" />
<property name="e5" column="E5" type="string" />
<property name="e6" column="E6" type="string" />
</joined-subclass>
<joined-subclass name="cn.test.hibernate3.demo1.ClassF" table="TABLE_F">
<key column="B_ID" />
<property name="f1" column="F1" type="string" />
<property name="f2" column="F2" type="string" />
<property name="f3" column="F3" type="string" />
<property name="f4" column="F4" type="string" />
<property name="f5" column="F5" type="string" />
<property name="f6" column="F6" type="string" />
<property name="f7" column="F7" type="string" />
</joined-subclass>
</class>
</hibernate-mapping>
在 ClassA.hbm.xml文件中,在用于映射ClassD 的<subclass>元素中还嵌入了两个<subclass>元素,它们分别映射ClassG和ClassHH类。在<class>及所有的<subclass>元系中都设置了discriminator-value属性,Hibernate根据discriminator-value属性来判断TABLE_A表中的记录对应哪个类的实例,如果TABLE_A表的一条记录的A_TYPE
子段取值为“A”,表明它是ClassA类的实例;如果A_TYPE字段取值为“G”,表明它是ClassG类的实例,依次类推。
子段取值为“A”,表明它是ClassA类的实例;如果A_TYPE字段取值为“G”,表明它是ClassG类的实例,依次类推。
测试类如下:
TestHbmExtend
package cn.test.hibernate3.demo1;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import cn.test.hibernate3.utils.HibernateUtils;
public class TestHbmExtend {
@Test
// 测试保存操作
public void demo1() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
ClassA g = new ClassG("a1", "d1", "g1");
ClassB f = new ClassF("b1", "f1", "f2", "f3", "f4", "f5", "f6", "f7");
session.save(g);
session.save(f);
tx.commit();
session.close();
}
}
生成表的结构如下:
插入的数据如下所示: