由于Java只允许一个类最多有一个直接的父类,因此Employ类、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).getSalary());
}
第一种方式:是将每个具体的类映射为一张表也是最简单的。
继承关系树的每个具体类对应一张表:这种映射方式关系数据模型完全不支持域模型中的继承关系和多态。
图 1 每个具体类对应的表
图 2 持久化类、映射文件和数据表之间的对应关系
如果Employee类不是抽象类(这里Employee类被设计成了抽象类),即Employee类也能被实例化,那么还需要为Employee类创建对应的Employees表,此时HOURLY_EMPLOYEES表和SALARIED_EMPLOYEES表结构仍然和图1相同,只不过多了一个EMPLOYEES表。
图 3 每个具体类对应的表
另外,还需要为Employee类创建单独的Employee.hbm.xml文件。
在这里我只讨论设计成常用的抽象类的模式。
从Company类到Employee类是多态关联,但是由于关系数据<nobr>模型</nobr>没有描述Employee类和它的两个子类的继承关系,因此无法映射Company类的employees集合。Company.hbm.xml文件的代码,该文件仅仅映射了Company类的id和name属性。
例程:Company.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<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类的关联关系。
例程:HourlyEmployee.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<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" column="RATE" type="double" />
<many-to-one
name="company"
column="COMPANY_ID"
class="mypack.Company"
/>
</class>
</hibernate-mapping>
SalariedEmployee.hbm.xml文件用于把SalariedEmployee类映射到SE表,在这个映射文件中,除了需要映射SalariedEmployee类本身的salary<nobr>属性</nobr>,还需要映射从Employee类中继承的name属性,此外还需要映射从Employee类中继承的与Company类的关联关系。
例程:SalariedEmployee.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<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" column="SALARY" type="double" />
<many-to-one
name="company"
column="COMPANY_ID"
class="mypack.Company"
/>
</class>
</hibernate-mapping>
由于Employee类没有映射文件,因此在初始化Hibernate时,只需向Configuration对象中加入Company类、HourlyEmployee类和SalariedEmployee类:
Configuration config=new Configuration();
config.addClass(Company.class)
.addClass(HourlyEmployee.class)
.addClass(SalariedEmployee.class);
这中方式不支持多态查询,所以
List employees=session.find("from Employee");
会抛出异常。因为Employee是抽象类,但如果Employee类是具体类的话也只会查询出EMPLOYEE表的记录不会检索出它的两个子类的实例。
源码如下:
hibernate.properties
hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class=com.mysql.jdbc.Driver
hibernate.connection.url=jdbc:mysql://localhost:3306/SAMPLEDB
hibernate.connection.username=root
hibernate.connection.password=1234
hibernate.show_sql=true
BusinessService.java
package mypack;
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.Configuration;
import java.util.*;
import java.sql.*;
public class BusinessService{
public static SessionFactory sessionFactory;
static{
try{
// Create a configuration based on the properties file we've put
Configuration config = new Configuration();
config.addClass(Company.class)
.addClass(HourlyEmployee.class)
.addClass(SalariedEmployee.class);
// Get the session factory we can use for persistence
sessionFactory = config.buildSessionFactory();
}catch(Exception e){e.printStackTrace();}
}
public void saveEmployee(Employee employee) throws Exception{
Session session = sessionFactory.openSession();
Transaction tx = null;
List results=new ArrayList();
try {
tx = session.beginTransaction();
session.save(employee);
tx.commit();
}catch (Exception e) {
if (tx != null) {
// Something went wrong; discard all partial changes
tx.rollback();
}
throw e;
} finally {
// No matter what, close the session
session.close();
}
}
public List findAllEmployees() throws Exception{
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
List results=new ArrayList();
tx = session.beginTransaction();
List hourlyEmployees=session.find("from HourlyEmployee");
results.addAll(hourlyEmployees);
List salariedEmployees=session.find("from SalariedEmployee");
results.addAll(salariedEmployees);
tx.commit();
return results;
}catch (Exception e) {
if (tx != null) {
// Something went wrong; discard all partial changes
tx.rollback();
}
throw e;
} finally {
// No matter what, close the session
session.close();
}
}
public Company loadCompany(long id) throws Exception{
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Company company=(Company)session.load(Company.class,new Long(id));
List hourlyEmployees=session.find("from HourlyEmployee h where h.company.id="+id);
company.getEmployees().addAll(hourlyEmployees);
List salariedEmployees=session.find("from SalariedEmployee s where s.company.id="+id);
company.getEmployees().addAll(salariedEmployees);
tx.commit();
return company;
}catch (Exception e) {
if (tx != null) {
// Something went wrong; discard all partial changes
tx.rollback();
}
throw e;
} finally {
// No matter what, close the session
session.close();
}
}
public void test() throws Exception{
List employees=findAllEmployees();
printAllEmployees(employees.iterator());
Company company=loadCompany(1);
printAllEmployees(company.getEmployees().iterator());
Employee employee=new HourlyEmployee("Mary",300,company);
saveEmployee(employee);
}
private void printAllEmployees(Iterator it){
while(it.hasNext()){
Employee e=(Employee)it.next();
if(e instanceof HourlyEmployee){
System.out.println(((HourlyEmployee)e).getRate());
}else
System.out.println(((SalariedEmployee)e).getSalary());
}
}
public static void main(String args[]) throws Exception {
new BusinessService().test();
sessionFactory.close();
}
}
Company.java
package mypack;
import java.io.Serializable;
import java.util.Set;
import java.util.HashSet;
public class Company implements Serializable {
private Long id;
private String name;
private Set employees=new HashSet();
/** full constructor */
public Company(String name, Set employees) {
this.name = name;
this.employees = employees;
}
/** default constructor */
public Company() {
}
/** minimal constructor */
public Company(Set employees) {
this.employees = employees;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Set getEmployees() {
return this.employees;
}
public void setEmployees(Set employees) {
this.employees = employees;
}
}
Company.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<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>
Employee.java
package mypack;
import java.io.Serializable;
abstract public class Employee implements Serializable {
private Long id;
private String name;
private Company company;
/** full constructor */
public Employee(String name,Company company) {
this.name = name;
this.company = company;
}
/** default constructor */
public Employee() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Company getCompany() {
return this.company;
}
public void setCompany(Company company) {
this.company = company;
}
}
HourlyEmployee.java
package mypack;
import java.io.Serializable;
public class HourlyEmployee extends Employee{
private double rate;
/** full constructor */
public HourlyEmployee(String name, double rate,Company company) {
super(name,company);
this.rate=rate;
}
/** default constructor */
public HourlyEmployee() {
}
public double getRate() {
return this.rate;
}
public void setRate(double rate) {
this.rate = rate;
}
}
HourlyEmployee.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<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" column="RATE" type="double" />
<many-to-one
name="company"
column="COMPANY_ID"
class="mypack.Company"
/>
</class>
</hibernate-mapping>
SalariedEmployee.java
package mypack;
import java.io.Serializable;
public class SalariedEmployee extends Employee {
private double salary;
/** full constructor */
public SalariedEmployee(String name, double salary,Company company) {
super(name,company);
this.salary=salary;
}
/** default constructor */
public SalariedEmployee() {
}
public double getSalary() {
return this.salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
SalariedEmployee.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<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" column="SALARY" type="double" />
<many-to-one
name="company"
column="COMPANY_ID"
class="mypack.Company"
/>
</class>
</hibernate-mapping>
sampledb.sql
drop database if exists SAMPLEDB;
create database SAMPLEDB;
use SAMPLEDB;
create table COMPANIES (
ID bigint not null,
NAME varchar(15),
primary key (ID)
);
create table HOURLY_EMPLOYEES (
ID bigint not null,
NAME varchar(15),
RATE double precision,
COMPANY_ID bigint,
primary key (ID)
);
create table SALARIED_EMPLOYEES (
ID bigint not null,
NAME varchar(15),
SALARY double precision,
COMPANY_ID bigint,
primary key (ID)
);
alter table HOURLY_EMPLOYEES add index IDX1_COMPANY(COMPANY_ID), add constraint FK1_COMPANY foreign key (COMPANY_ID) references COMPANIES (ID);
alter table SALARIED_EMPLOYEES add index IDX2_COMPANY(COMPANY_ID), add constraint FK2_COMPANY foreign key (COMPANY_ID) references COMPANIES (ID);
insert into COMPANIES(ID,NAME) values(1,'ABC Company');
insert into HOURLY_EMPLOYEES(ID,NAME,RATE,COMPANY_ID) values(1,'Tom',100,1);
insert into HOURLY_EMPLOYEES(ID,NAME,RATE,COMPANY_ID) values(2,'Mike',200,1);
insert into SALARIED_EMPLOYEES(ID,NAME,SALARY,COMPANY_ID)
values(1,'Jack',5000,1);
insert into SALARIED_EMPLOYEES(ID,NAME,SALARY,COMPANY_ID)
values(2,'Linda',6000,1);
总结:
BusinessServic的main()方法调用test()方法,test()方法依次调用以下方法。
* findAllEmployees():检索数据库中所有的Employee对象
* loadCompany():加载一个Company对象
* saveEmployee():保存一个Employee对象
(1) 运行findAllEmployee()方法,它的代码如下:
List results=new ArrayList();
tx=session.beginTransaction();
List hourlyEmployees=session.find("from HourlyEmployee");
results.addAll(hourlyEmployee);
List salariedEmployees=session.find("from SalariedEmployee");
results.addAll(salariedEmployees);
tx.commit();
return results;
为了检索所有的Employee对象,必须分别检索所有的HourlyEmployee实例和SalariedEmployee实例,然后将它们合并到一个集合中去。在运行Session的第一个find()方法时,Hibernate执行以下select语句:
select * from HOURLY_EMPLOYEES;
select * from COMPANIES where ID=1;
从HourlyEmployee类到Company类不是多态关联,在加载HourlyEmployee对象时,会同时加载与它关联的Company对象。
在运行Session的第二个find()方法时,Hibernate执行以下select语句:
select * from SALARIED_EMPLOYEES;
从SalariedEmployee类到Company类不是多态关联,在加载SalariedEmployee对象时,会同时加载与它关联的Company对象。在本测试数据中,所有的HourlyEmployee实例和SalariedEmployee实例都与OID为1的Company对象关联,由于该Company对象已经被加载到内存中,所以Hibernate不再需要执行检索该对象的 select语句。
(2) 运行loadCompany()方法,它的代码如下:
tx=session.beginTransaction();
Company company=(Company)session.load(Company.class,new Long(id));
List hourlyEmployees=session.find("from HourlyEmployee h where h.company.id="+id);
company.getEmployees().addAll(hourlyEmployees);
//抽象类不能用new来创建
List salariedEmployees=session.find("from SalariedEmployee s where s.company.id="+id);
company.getEmployees().addAll(salariedEmployees);
tx.commit();
return company;
由于这种映射方式不支持多态关联,因此由Session的load()方法加载的Company<nobr>对象</nobr>的employees<nobr>集合</nobr>中不包含任何Employee对象。BusinessService类必须负责从数据库中检索出所有与Company对象关联的HourlyEmployee对象以及SalariedEmployee对象,然后把它们加入到employees集合中。
(3) 运行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实例,就向HE表中插入一条记录,执行如下insert<nobr>语句</nobr>:
insert into HOURLY_EMPLOYEES (ID,NAME,RATE,CUSTOMER_ID) values(3,'Mary',300,1);
如果employee<nobr>变量</nobr>引用SalariedEmployee<nobr>实例</nobr>,就向SE表插入一条记录。