黑蜘蛛专栏

IT技术交流中心

nely ID:gnemy
12136次访问,排名9208(1)好友0人,关注者0
gnemy的文章
原创 21 篇
翻译 0 篇
转载 3 篇
评论 4 篇
黑蜘蛛的公告
不要成为只能写代码的程序员; 不要成为无法与其他人合作的程序员; 不要成为只能做程序员的程序员。 程序员不一定必须持有程序员证书, 作为程序员最为重要的是具有创新、钻研、好学等精神和能写出好的程序.
最近评论
Aguo_C:原来这样就行啦,谢谢啦!
csvivi:原来要这啊~~呵呵,谢谢啦~
gnemy:test
soyea910:你好!目前上海华普汽车正在进行“海炫《女性.车》博客大赛”,原创的文章、照片都可参加,大奖:最新款海炫女性车一辆,参加者人人有奖品,建议博主去看一下。参赛地址:http://hyshine.bokee.net 发这个帖子没什么恶意只是想让更多的人看到去参加!!
文章分类
收藏
    相册
    .net
    ajax
    ajax china
    DWR - Easy AJAX for JAVA(RSS)
    Edit-in-Place with jQuery(RSS)
    jeditable(RSS)
    jquery1.0(RSS)
    jquery1.1(RSS)
    jquery-jeditable(RSS)
    c
    c++
    freemarker
    freemarker技术帮助(RSS)
    java
    Url Rewrite Filter
    web.xml
    扩展Lucene的索引文件存储
    vb
    技术
    ruby语言只是昙花一现 (RSS)
    firefox插件 可查看页面执行代码(RSS)
    http://wz.csdn.net/malligator/(RSS)
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 映射继承关系收藏

    新一篇: 有效编写软件的75条建议  | 旧一篇: java开发环境配置

    第14章 映射继承关系
    在域模型中,类与类之间除了关联关系和聚集关系,还可以存在继承关系,在图14-1所示的域模型中,Company类和Employee类之间为一对多的双向关联关系(假定不允许雇员同时在多个公司兼职),Employee类为抽象类,因此它不能被实例化,它有两个具体的子类:HourlyEmployee类和SalariedEmployee类。由于Java只允许一个类最多有一个直接的父类,因此Employee类、HourlyEmployee类和SalariedEmployee类构成了一棵继承关系树。

    图14-1 包含继承关系的域模型
    在面向对象的范畴中,还存在多态的概念,多态建立在继承关系的基础上。简单地理解,多态是指当一个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).getSalary());
    }
    BusinessService类的findAllEmployees()方法通过Hibernate API从数据库中检索出所有Employee对象。findAllEmployees()方法返回的集合既包含HourlyEmployee类的实例,也包含SalariedEmployee类的实例,这种查询被称为多态查询。以上程序中变量e被声明为Employee类型,它实际上既可能引用HourlyEmployee类的实例,也可能引用SalariedEmployee类的实例。
    此外,从Company类到Employee类为多态关联,因为Company类的employees集合中可以包含HourlyEmployee类和SalariedEmployee类的实例。从Employee类到Company类不是多态关联,因为Employee类的company属性只会引用Company类本身的实例。
    数据库表之间并不存在继承关系,那么如何把域模型的继承关系映射到关系数据模型中呢?本章将介绍以下三种映射方式:
    l 继承关系树的每个具体类对应一个表:关系数据模型完全不支持域模型中的继承关系和多态。
    l 继承关系树的根类对应一个表:对关系数据模型进行非常规设计,在数据库表中加入额外的区分子类型的字段。通过这种方式,可以使关系数据模型支持继承关系和多态。
    l 继承关系树的每个类对应一个表:在关系数据模型中用外键参照关系来表示继承关系。
    具体类是指非抽象的类,具体类可以被实例化。HourlyEmployee类和SalariedEmployee类就是具体类。
    以上每种映射方式都有利有弊,本章除了介绍每种映射方式的具体步骤,还介绍了它们的适用范围。
    14.1 继承关系树的每个具体类对应一个表
    把每个具体类映射到一张表是最简单的映射方式。如图14-2所示,在关系数据模型中只需定义COMPANIES、HOURLY_EMPLOYEES和SALARIED_EMPLOYEES表。为了叙述的方便,下文把HOURLY_EMPLOYEES表简称为HE表,把SALARIED_EMPLOYEES表简称为SE表。HourlyEmployee类和HE表对应,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类没有相应的映射文件。图14-3显示了持久化类、映射文件和数据库表之间的对应关系。











    图14-2 每个具体类对应一个表







    图14-3 持久化类、映射文件和数据库表之间的对应关系
    如果Employee类不是抽象类,即Employee类本身也能被实例化,那么还需要为Employee类创建对应的EMPLOYEES表,此时HE表和SE表的结构仍然和图14-2中所示的一样。这意味着在EMPLOYEES表、HE表和SE表中都定义了相同的NAME字段以及参照COMPANIES表的外键COMPANY_ID。另外,还需为Employee类创建单独的Employee.hbm.xml文件。
    14.1.1 创建映射文件
    从Company类到Employee类是多态关联,但是由于关系数据模型没有描述Employee类和它的两个子类的继承关系,因此无法映射Company类的employees集合。例程14-1是Company.hbm.xml文件的代码,该文件仅仅映射了Company类的id和name属性。
    例程14-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类的关联关系。例程14-2是HourlyEmployee.hbm.xml文件的代码。
    例程14-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" 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属性,还需要映射从Employee类中继承的name属性,此外还要映射从Employee类中继承的与Company类的关联关系。例程14-3是SalariedEmployee.hbm.xml文件的代码。
    例程14-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" 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);
    14.1.2 操纵持久化对象
    这种映射方式不支持多态查询,在本书第11章的11.1.6节(多态查询)介绍了多态查询的概念。对于以下查询语句:
    List employees=session.find("from Employee");
    如果Employee类是抽象类,那么Hibernate会抛出异常。如果Employee类是具体类,那么Hibernate仅仅查询EMPLOYEES表,检索出Employee类本身的实例,但不会检索出它的两个子类的实例。本节的范例程序位于配套光盘的sourcecode\chapter14\14.1目录下,运行该程序前,需要在SAMPLEDB数据库中手工创建COMPANIES表、HE表和SE表,然后加入测试数据,相关的SQL脚本文件为\14.1\schema/sampledb.sql。
    在chapter14目录下有四个ANT的工程文件,分别为build1.xml、build2.xml、build3.xml和build4.xml,它们的区别在于文件开头设置的路径不一样,例如在build1.xml文件中设置了以下路径:
    <property name="source.root" value="14.1/src"/>
    <property name="class.root" value="14.1/classes"/>
    <property name="lib.dir" value="lib"/>
    <property name="schema.dir" value="14.1/schema"/>
    在DOS命令行下进入chapter14根目录,然后输入命令:
    ant -file build1.xml run
    就会运行BusinessService类。ANT命令的-file选项用于显式指定工程文件。BusinessService类用于演示操纵Employee类的对象的方法,例程14-4是它的源程序。
    例程14-4 BusinessService.java
    public class BusinessService{
    public static SessionFactory sessionFactory;
    static{
    try{
    Configuration config = new Configuration();
    config.addClass(Company.class)
    .addClass(HourlyEmployee.class)
    .addClass(SalariedEmployee.class);
    sessionFactory = config.buildSessionFactory();
    }catch(Exception e){e.printStackTrace();}
    }

    public void saveEmployee(Employee employee) throws Exception{……}
    public List findAllEmployees() throws Exception{……}
    public Company loadCompany(long id) throws Exception{……}

    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();
    }
    }
    BusinessService的main()方法调用test()方法,test()方法依次调用以下方法。
    l findAllEmployees():检索数据库中所有的Employee对象。
    l loadCompany():加载一个Company对象。
    l saveEmployee():保存一个Employee对象。
    (1)运行findAllEmployees()方法,它的代码如下:
    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;
    为了检索所有的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);

    List salariedEmployees=session.find("from SalariedEmployee s where s.company.id="+id);
    company.getEmployees().addAll(salariedEmployees);

    tx.commit();
    return company;
    由于这种映射方式不支持多态关联,因此由Session的load()方法加载的Company对象的employees集合中不包含任何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语句:
    insert into HOURLY_EMPLOYEES(ID,NAME,RATE,CUSTOMER_ID)
    values(3, 'Mary',300,1);
    如果employee变量引用SalariedEmployee实例,就向SE表插入一条记录。
    14.2 继承关系树的根类对应一个表
    这种映射方式只需为继承关系树的Employee根类创建一张表EMPLOYEES。如图14-4所示,在EMPLOYEES表中不仅提供和Employee类的属性对应的字段,还要提供和它的两个子类的所有属型对应的字段,此外,EMPLOYEES表中需要额外加入一个字符串类型的EMPLOYEE_TYPE字段,用于区分Employee的具体类型。






    图14-4 继承关系树的根类对应一个表
    Company类和Employee类有相应的映射文件,而HourlyEmployee类和SalariedEmployee类没有相应的映射文件。图14-5显示了持久化类、映射文件和数据库表之间的对应关系。







    图14-5 持久化类、映射文件和数据库表之间的对应关系

    14.2.1 创建映射文件
    从Company类到Employee类是多态关联,由于关系数据模型描述了Employee类和它的两个子类的继承关系,因此可以映射Company类的employees集合。例程14-5是Company.hbm.xml文件的代码,该文件不仅映射了Company类的id和name属性,还映射了它的employees集合。
    例程14-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"
    lazy="true" >
    <key column="COMPANY_ID" />
    <one-to-many class="mypack.Employee" />
    </set>

    </class>
    </hibernate-mapping>
    Employee.hbm.xml文件用于把Employee类映射到EMPLOYEES表,在这个映射文件中,除了需要映射Employee类本身的属性,还需要在<subclass>元素中映射两个子类的属性。例程14-6是Employee.hbm.xml文件的代码。
    例程14-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" column="RATE" type="double" />
    </subclass>

    <subclass name="mypack.SalariedEmployee" discriminator-value="SE" >
    <property name="salary" column="SALARY" type="double" />
    </subclass>

    </class>

    </hibernate-mapping>
    在Employee.hbm.xml文件中,<discriminator>元素指定EMPLOYEES表中用于区分Employee类型的字段为EMPLOYEE_TYPE,两个<subclass>元素用于映射HourlyEmployee类和SalariedEmployee类,<subclass>元素的discriminator-value属性指定EMPLOYEE_TYPE字段的取值。EMPLOYEES表中有以下记录:

    ID NAME EMPLOYEE_TYPE RATE SALARY COMPANY_ID

    1 Tom HE 100 NULL 1
    2 Mike HE 200 NULL 1
    3 Jack SE NULL 5000 1
    4 Linda SE NULL 6000 1

    其中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约束,可见这种映射方式无法保证关系数据模型的数据完整性。
    由于HourlyEmployee类和SalariedEmployee类没有单独的映射文件,因此在初始化Hibernate时,只需向Configuration对象中加入Company类和Employee类:
    Configuration config = new Configuration();
    config.addClass(Company.class)
    .addClass(Employee.class);
    如果Employee类不是抽象类,即它本身也能被实例化,那么可以在<class>元素中定义它的discriminator值,形式如下:
    <class name="mypack.Employee" table="EMPLOYEES" discriminator-value="EE">
    以上代码表明,如果EMPLOYEES表中一条记录的EMPLOYEE_TYPE字段的取值为“EE”,那么它对应Employee类本身的实例。
    14.2.2 操纵持久化对象
    这种映射方式支持多态查询,对于以下查询语句:
    List employees=session.find("from Employee");
    Hibernate会检索出所有的HourlyEmployee对象和SalariedEmployee对象。此外,也可以单独查询Employee类的两个子类的实例,例如:
    List hourlyEmployees=session.find("from HourlyEmployee");
    本节的范例程序位于配套光盘的sourcecode\chapter14\14.2目录下,运行该程序前,需要在SAMPLEDB数据库中手工创建COMPANIES表和EMPLOYEES表,然后加入测试数据,相关的SQL脚本文件为/14.2\schema\sampledb.sql。
    在DOS命令行下进入chapter14根目录,然后输入命令:
    ant -file build2.xml run
    就会运行BusinessService类。BusinessService的main()方法调用test()方法,test()方法依次调用以下方法:
    l findAllHourlyEmployees():检索数据库中所有的HourlyEmployee对象。
    l findAllEmployees():检索数据库中所有的Employee对象。
    l loadCompany():加载一个Company对象。
    l saveEmployee():保存一个Employee对象。
    (1)运行findAllHourlyEmployees()方法,它的代码如下:
    tx = session.beginTransaction();
    List results=session.find("from HourlyEmployee");
    tx.commit();
    return results;
    在运行Session的find()方法时,Hibernate执行以下select语句:
    select * from EMPLOYEES where EMPLOYEE_TYPE='HE' ;
    select * from COMPANIES where ID=1;
    在加载HourlyEmployee对象时,还会同时加载与它关联的Company对象。
    (2)运行findAllEmployees()方法,它的代码如下:
    tx = session.beginTransaction();
    List results=session.find("from Employee");
    tx.commit();
    return results;
    在运行Session的find()方法时,Hibernate执行以下select语句:
    select * from EMPLOYEES;
    select * from COMPANIES where ID=1;
    在这种映射方式下,Hibernate支持多态查询,对于从EMPLOYEES表获得的查询结果,如果EMPLOYEE_TYPE字段取值为“HE”,就创建HoulyEmployee实例,如果EMPLOYEE_TYPE字段取值为“SE”,就创建SalariedEmployee实例,这些实例所关联的Company对象也被加载。
    (3)运行loadCompany()方法,它的代码如下:
    tx = session.beginTransaction();
    Company company=(Company)session.load(Company.class,new Long(id));
    Hibernate.initialize(company.getEmployees());
    tx.commit();
    这种映射方式支持多态关联。如果在Company.hbm.xml文件中对employees集合设置了立即检索策略,那么Session的load()方法加载的Company对象的employees集合中包含所有关联的Employee对象。由于本书提供的Company.hbm.xml文件对employees集合设置了延迟检索策略,因此以上程序代码还通过Hibernate类的静态initialize()方法来显式初始化employees集合。
    (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,RATE,EMPLOYEE_TYPE,CUSTOMER_ID)
    values(5, 'Mary ',300, 'HE',1);
    以上insert语句没有为SalariedEmployee类的salary属性对应的SALARY字段赋值,因此这条记录的SALARY字段为null。
    14.3 继承关系树的每个类对应一个表
    在这种映射方式下,继承关系树的每个类以及接口都对应一个表。在本例中,需要创建EMPLOYEES、HE和SE表。如图14-6所示,EMPLOYEES表仅包含和Employee类的属性对应的字段,HE表仅包含和HourlyEmployee类的属性对应的字段,SE表仅包含和SalariedEmployee类的属性对应的字段。此外,HE表和SE表都以EMPLOYEE_ID字段作为主键,该字段还同时作为外键参照EMPLOYEES表。













    图14-6 继承关系树的每个类对应一个表
    Company类和Employee类有相应的映射文件,而HourlyEmployee类和SalariedEmployee类没有相应的映射文件。图14-7显示了持久化类、映射文件和数据库表之间的对应关系。







    图14-7 持久化类、映射文件和数据库表之间的对应关系
    14.3.1 创建映射文件
    从Company类到Employee类是多态关联,由于关系数据模型描述了Employee类和它的两个子类的继承关系,因此可以映射Company类的employees集合。例程14-7是Company.hbm.xml文件的代码,该文件不仅映射了Company类的id和name属性,还映射了它的employees集合。
    例程14-7 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"
    lazy="true" >
    <key column="COMPANY_ID" />
    <one-to-many class="mypack.Employee" />
    </set>

    </class>
    </hibernate-mapping>
    Employee.hbm.xml文件用于把Employee类映射到EMPLOYEES表,在这个映射文件中,除了需要映射Employee类本身的属性,还需要在<joined-subclass>元素中映射两个子类的属性。例程14-8是Employee.hbm.xml文件的代码。
    例程14-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="EMPLOYEE_ID" />
    <property name="rate" column="RATE" type="double" />
    </joined-subclass>

    <joined-subclass name="mypack.SalariedEmployee" table="SALARIED_EMPLOYEES" >
    <key column="EMPLOYEE_ID" />
    <property name="salary" column="SALARY" type="double" />
    </joined-subclass>

    </class>
    </hibernate-mapping>
    在Employee.hbm.xml文件中,两个<joined-subclass>元素用于映射HourlyEmployee类和SalariedEmployee类,<joined-subclass>元素的<key>子元素指定HE表和SE表中既作为主键又作为外键的EMPLOYEE_ID字段。图14-8显示了EMPLOYEES表、HE表和SE表中记录的参照关系。

    图14-8 EMPLOYEES表、HE表和SE表中记录的参照关系
    由于HourlyEmployee类和SalariedEmployee类没有单独的映射文件,因此在初始化Hibernate时,只需向Configuration对象中加入Company类和Employee类:
    Configuration config = new Configuration();
    config.addClass(Company.class)
    .addClass(Employee.class);
    也可以在单独的映射文件中配置<subclass>或<joined-subclass>元素,但此时必须显式设定它们的extends属性。例如可以在单独的HourlyEmployee.hbm.xml文件中映射HourlyEmployee类:
    <hibernate-mapping >
    <joined-subclass
    name="mypack.HourlyEmployee"
    table="HOURLY_EMPLOYEES"
    extends="mypack.Employee" >
    ……
    </joined-class>
    <hibernate-mapping >
    由于HourlyEmployee类的映射代码不位于Employee.hbm.xml文件中,因此在初始化Hibernate时,不仅需要向Configuration对象中加入Company类和Employee类,还需要加入HourlyEmployee类,并且必须先加入Employee父类,再加入HourlyEmployee子类:
    Configuration config = new Configuration();
    config.addClass(Company.class)
    .addClass(Employee.class)
    .addClass(HourlyEmployee.class);
    如果颠倒加入Employee类和HourlyEmployee子类的顺序,Hibernate在执行addClass()方法时会抛出HibernateMappingException。
    14.3.2 操纵持久化对象
    这种映射方式支持多态查询,对于以下查询语句:
    List employees=session.find("from Employee");
    Hibernate会检索出所有的HourlyEmployee对象和SalariedEmployee对象。此外,也可以单独查询Employee类的两个子类的实例,例如:
    List hourlyEmployees=session.find("from HourlyEmployee");
    本节的范例程序位于配套光盘的sourcecode\chapter14\14.3目录下,运行该程序前,需要在SAMPLEDB数据库中手工创建COMPANIES表、EMPLOYEES表、HE表和SE表,然后加入测试数据,相关的SQL脚本文件为\14.3\schema\sampledb.sql。
    在DOS命令行下进入chapter14根目录,然后输入命令:
    ant -file build3.xml run
    就会运行BusinessService类。BusinessService的main()方法调用test()方法,test()方法依次调用以下方法:
    l findAllHourlyEmployees():检索数据库中所有的HourlyEmployee对象。
    l findAllEmployees():检索数据库中所有的Employee对象。
    l loadCompany():加载一个Company对象。
    l saveEmployee():保存一个Employee对象。
    (1)运行findAllHourlyEmployees()方法,它的代码如下:
    tx = session.beginTransaction();
    List results=session.find("from HourlyEmployee");
    tx.commit();
    return results;
    在运行Session的find()方法时,Hibernate执行以下select语句:
    select * from HOURLY_EMPLOYEES he inner join EMPLOYEES e
    on he.EMPLOYEE_ID=e.ID;
    select * from COMPANIES where ID=1;
    Hibernate通过HE表与EMPLOYEES表的内连接获得HourlyEmployee对象的所有属性值,此外,在加载HourlyEmployee对象时,还会同时加载与它关联的Company对象。
    (2)运行findAllEmployees()方法,它的代码如下:
    tx = session.beginTransaction();
    List results=session.find("from Employee");
    tx.commit();
    return results;
    在运行Session的find()方法时,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;


    select * from COMPANIES where ID=1;
    Hibernate把EMPLOYEES表与HE表以及SE表进行左外连接,从而获得HourlyEmployee对象和SalariedEmployee对象的所有属性值。在这种映射方式下,Hibernate支持多态查询,对于以上查询语句获得的查询结果,如果HE表的EMPLOYEE_ID字段不为null,就创建HoulyEmployee实例,如果SE表的EMPLOYEE_ID字段不为null,就创建SalariedEmployee实例,这些实例所关联的Company对象也被加载。
    (3)运行loadCompany()方法,它的代码如下:
    tx = session.beginTransaction();
    Company company=(Company)session.load(Company.class,new Long(id));
    Hibernate.initialize(company.getEmployees());
    tx.commit();
    这种映射方式支持多态关联。如果在Company.hbm.xml文件中对employees集合设置了立即检索策略,那么Session的load()方法加载的Company对象的employees集合中包含所有关联的Employee对象。由于本书提供的Company.hbm.xml文件对employees集合设置了延迟检索策略,因此以上程序代码还通过Hibernate类的静态initialize()方法来显式初始化employees集合。
    (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表的记录共享同一个主键。
    14.4 选择继承关系的映射方式
    本章介绍的三种映射方式各有优缺点,表14-1对这三种映射方式做了比较。

    表14-1 比较三种映射方式
    比较方面 每个具体类对应一个表 根类对应一个表 每个类对应一个表
    关系数据模型的复杂度 缺点:每个具体类对应一个表,这些表中包含重复字段 优点:只需创建一个表 缺点:表的数目最多,并且表之间还有外键参照关系
    查询性能 缺点:如果要查询父类的对象,必须查询所有具体的子类对应的表 优点:有很好的查询性能,无需进行表的连接 缺点:需要进行表的内连接或左外连接
    数据库Schema的可维护性 缺点:如果父类的属性发生变化,必须修改所有具体的子类对应的表 优点:只需修改一张表 优点:如果某个类的属性发生变化,只需修改和这个类对应的表
    是否支持多态查询和多态关联 缺点:不支持 优点:支持 优点:支持
    是否符合关系数据模型的常规设计规则 优点:符合 缺点:(1)在表中引入额外的区分子类的类型的字段(2)如果子类中的某个属性不允许为null,在表中无法为对应的字段创建not null约束 优点:符合
    如果不需要支持多态查询和多态关联,可以采用每个具体类对应一个表的映射方式,如果需要支持多态查询和多态关联,并且子类包含的属性不多,可以采用根类对应一个表的映射方式,如果需要支持多态查询和多态关联,并且子类包含的属性很多,可以采用每个类对应一个表的映射方式。如果继承关系树中包含接口,可以把它当做抽象类来处理。
    图14-9显示了一棵复杂的继承关系树,其中DOClass类为抽象类,其他均为具体类。

    图14-9 复杂的继承关系树
    可以将图14-9的继承关系树分解为3棵子树:
    l DOClass类、ClassA类和ClassB类为一棵子树:DOClass类为抽象类,位于整个继承关系树的顶层,通常不会对它进行多态查询,因此可以采用每个具体类对应一个表的映射方式,ClassA类对应TABLE_A表,ClassB类对应TABLE_B表。
    l ClassA类、ClassC类、ClassD类、ClassG类和ClassH类为一棵子树:ClassA类的所有子类都只包含少量属性,因此可以采用根类对应一个表的映射方式,ClassA类对应TABLE_A表。
    l ClassB类、ClassE类和ClassF为一棵子树:ClassB类的两个子类都包含很多属性,因此采用每个类对应一个表的映射方式,ClassB类对应TABLE_B表,ClassE类对应TABLE_E表,ClassF类对应TABLE_F表。
    如图14-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的B_ID字段既是主键,又是参照TABLE_B表的外键。








    图14-10 复杂继承关系树对应的关系数据模型
    只需创建两个映射文件,ClassA.hbm.xml和ClassB.hbm.xml,例程14-9和例程14-10分别为它们的源代码。
    例程14-9 ClassA.hbm.xml
    <hibernate-mapping >
    <class name="mypack.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="mypack.ClassC" discriminator-value="C" >
    <property name="c1" column="C1" type="string" />
    </subclass>

    <subclass name="mypack.ClassD" discriminator-value="D" >
    <property name="d1" column="D1" type="string" />

    <subclass name="mypack.ClassG" discriminator-value="G" >
    <property name="g1" column="G1" type="string" />
    </subclass>

    <subclass name="mypack.ClassH" discriminator-value="H" >
    <property name="h1" column="H1" type="string" />
    </subclass>
    </subclass>
    </class>
    </hibernate-mapping>

    例程14-10 ClassB.hbm.xml
    <hibernate-mapping >

    <class name="mypack.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="mypack.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="mypack.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" />
    </joined-subclass >
    </class>
    </hibernate-mapping>
    在ClassA.hbm.xml文件中,在用于映射ClassD的<subclass>元素中还嵌入了两个<subclass>元素,它们分别映射ClassG和ClassH类。在<class>以及所有的<subclass>元素中都设置了discriminator-value属性,Hibernate根据discriminator-value属性来判断TABLE_A表中的记录对应哪个类的实例,如果 TABLE_A表的一条记录的A_TYPE字段取值为“A”,表明它是ClassA类的实例,如果A_TYPE字段取值为“G”,表明它是ClassG类的实例,依次类推。
    值得注意的是,在<subclass>元素中只能嵌入<subclass>子元素,但不能嵌入<joined-subclass>子元素,而在<joined-subclass>元素中只能嵌入<joined-subclass>子元素,但不能嵌入< subclass>子元素。
    本节的范例程序位于配套光盘的sourcecode\chapter14\14.4目录下,运行该程序前,需要在SAMPLEDB数据库中手工创建TABLE_A表、TABLE_B表、TABLE_E表和TABLE_F表,相关的SQL脚本文件为\14.4\schema\sampledb.sql。
    在DOS命令行下进入chapter14根目录,然后输入命令:
    ant -file build4.xml run
    就会运行BusinessService类。BusinessService的main()方法调用test()方法,test()方法调用saveDO(DOClass Object)方法,它负责保存一个DOClass对象,saveDO()方法的代码如下:
    tx = session.beginTransaction();
    session.save(object);
    tx.commit();
    在test()方法中,创建了一个ClassG类的实例和一个ClassF类的实例,然后调用saveDO()方法分别保存这两个实例:
    ClassG g=new ClassG("a1","d1","g1");
    saveDO(g);

    ClassF f=new ClassF("b1","f1","f2","f3","f4","f5","f6","f7");
    saveDO(f);
    Session的save()方法能判断object变量实际引用的实例的类型,如果object变量引用ClassG类的实例,就执行如下insert语句:
    insert into TABLE_A (ID,A1,D1,G1,A_TYPE) values (1, 'a1', 'd1', 'g1','G');
    如果object变量引用ClassF类的实例,就执行如下insert语句:
    insert into TABLE_B (ID,B1) values (1, 'b1');
    insert into TABLE_F (B_ID ,F1, F2, F3, F4, F5, F6) values (1, 'f1', 'f2', 'f3',
    'f4', 'f5', 'f6', 'f7');

    14.5 映射多对一多态关联
    Company与Employee类之间为一对多多态关联关系,如果继承关系树的根类对应一个表,或者每个类对应一个表,那么就能映射Company类的employees集合。本节介绍如何映射多对一多态关联。如图14-11所示,ClassD与ClassA为多对一多态关联关系。

    图14-11 ClassD与ClassA为多对一多态关联关系
    ClassA、ClassB和ClassC构成了一棵继承关系树,如果继承关系树的根类对应一个表,或者每个类对应一个表,那么可以按以下方式映射ClassD的a属性:
    <many-to-one name="a"
    class="ClassA"
    column="A_ID"
    cascade="save-update" />
    假定与ClassD对应的表为TABLE_D,与ClassA对应的表为TABLE_A,在TABLE_D中定义了外键A_ID,它参照TABLE_A表的主键。
    ClassD对象的a属性既可以引用ClassB对象,也可以引用ClassC对象,例如:
    tx = session.beginTransaction();
    ClassD d=(ClassD)session.get("ClassD",id);
    ClassA a=d.getA();
    if(a instanceof ClassB)
    System.out.println(((ClassB)a).getB1());
    if(a instanceof ClassC)
    System.out.println(((ClassC)a).getC1());
    tx.commit();
    以下代码在映射ClassD类的a属性时使用了延迟检索策略:
    <many-to-one name="a"
    class="ClassA"
    column="A_ID"
    lazy="true"
    cascade="save-update" />
    当Hibernate加载ClassD对象时,它的属性a引用ClassA的代理类实例,在这种情况下,如果对ClassA的代理类实例进行类型转换,会抛出ClassCastException:
    ClassA a=d.getA();
    ClassB b=(ClassB)a; //抛出ClassCastException
    解决以上问题的一种办法是使用Session.load()方法:
    ClassA a=d.getA();
    ClassB b=(ClassB)session.load(ClassB.class,a.getId());
    System.out.println(b.getB1());
    当执行Session的load()方法时,Hibernate并不会访问数据库,而是仅仅返回ClassB的代理类实例。这种解决办法的前提条件是必须事先知道ClassD对象实际上和ClassA的哪个子类的对象关联。
    解决以上问题的另一种办法是显式使用迫切左外连接检索策略,避免Hibernate创建ClassA的代理类实例,而是直接创建ClassA的子类的实例:
    tx = session.beginTransaction();
    ClassD d=(ClassD)session.createCriteria(ClassD.class)
    .add(Expression.eq("id",id))
    .setFetchMode("a",FetchMode.EAGER)
    .uniqueResult();
    ClassA a=d.getA();
    if(a instanceof ClassB)
    System.out.println(((ClassB)a).getB1());
    if(a instanceof ClassC)
    System.out.println(((ClassC)a).getC1());
    tx.commit();
    如果继承关系树的具体类对应一个表,为了表达ClassD与ClassA的多态关联,需要在TABLE_D中定义两个字段:A_ID和A_TYPE,A_TYPE字段表示子类的类型,A_ID参照在子类对应的表中的主键。图14-12显示了表TABLE_D、TABLE_B和TABLE_C的结构。










    图14-12 表TABLE_D、TABLE_B和TABLE_C的结构
    由于关系数据模型不允许一个表的外键同时参照两个表的主键,因此无法对TABLE_D表的A_ID字段定义外键参照约束,而应该通过其他方式,如触发器,来保证A_ID字段的参照完整性。由于TABLE_D表的A_ID字段既可能参照TABLE_B表的ID主键,也可能参照TABLE_C表的ID主键,要求TABLE_B表和TALBE_C表的ID主键具有相同的SQL类型。
    在ClassD.hbm.xml文件中,用<any>元素来映射ClassD的a属性:
    <any name="a"
    meta-type="string"
    id-type="long"
    cascade="save-update">
    <meta-value value="B" class="ClassB" />
    <meta-value value="C" class="ClassC" />
    <column name="A_TYPE" />
    <column name="A_ID" />
    </any>
    <any>元素的meta-type属性指定TABLE_D中A_TYPE字段的类型,id-type属性指定TABLE_D中A_ID字段的类型,<meta-value>子元素设定A_TYPE字段的可选值。在本例中,如果A_TYPE字段取值为“B”,表示为ClassB的对象,A_ID字段参照TABLE_B表中的ID主键;如果A_TYPE字段取值为“C”,表示为ClassC的对象,A_ID字段参照TABLE_C表中的ID主键。<column>子元素指定TABLE_D表中的A_TYPE字段和A_ID字段,必须先指定A_TYPE字段,再指定A_ID字段。
    14.6 小 结
    本章介绍了映射继承关系的三种方式:
    l 继承关系树的每个具体类对应一个表:在具体类对应的表中,不仅包含和具体类的属性对应的字段,还包含和具体类的父类的属性对应的字段。这种映射方式不支持多态关联和多态查询。
    l 继承关系树的根类对应一个表:在根类对应的表中,不仅包含和根类的属性对应的字段,还包含和所有子类的属性对应的字段。这种映射方式支持多态关联和多态查询,并且能获得最佳查询性能,缺点是需要对关系数据模型进行非常规设计,在数据库表中加入额外的区分各个子类的字段,此外,不能为所有子类的属性对应的字段定义not null约束。
    l 继承关系树的每个类对应一个表:在每个类对应的表中只需包含和这个类本身的属性对应的字段,子类对应的表参照父类对应的表。这种映射方式支持多态关联和多态查询,而且符合关系数据模型的常规设计规则,缺点是它的查询性能不如第二种映射方式。在这种映射方式下,必须通过表的内连接或左外连接来实现多态查询和多态关联。
    在默认情况下,对于简单的继承关系树可以采用根类对应一个表的映射方式。如果必须保证关系数据模型的数据完整性,可以采用每个类对应一个表的映射方式。对于复杂的继承关系树,可以将它分解为几棵子树,对每棵子树采用不同的映射方式。当然,在设计域模型时,应该尽量避免设计过分复杂的继承关系,这不仅会增加把域模型映射到关系数据模型的难度,而且也会增加在Java程序代码中操纵持久化对象的复杂度。
    对于不同的映射方式,必须创建不同的关系数据模型和映射文件,但是域模型是一样的,域模型中的持久化类的实现也都一样。只要具备Java编程基础知识,就能创建具有继承关系的持久化类,因此本章没有详细介绍这些持久化类的创建过程,在此仅提醒一点,子类的完整构造方法不仅负责初始化子类本身的属性,还应该负责初始化从父类中继承的属性,例如以下是HourlyEmployee类的构造方法:
    public class HourlyEmployee extends Employee{
    private double rate;

    /** 完整构造方法*/
    public HourlyEmployee(String name, double rate,Company company) {
    super(name,company);
    this.rate=rate;
    }

    /** 默认构造方法*/
    public HourlyEmployee() {}
    ……
    }
    Hibernate只会访问持久化类的默认构造方法,永远不会访问其他形式的构造方法。提供以上形式的完整构造方法,主要是为Java应用的编程提供方便。 
     

    发表于 @ 2006年12月08日 12:52:00|评论(loading...)|编辑

    新一篇: 有效编写软件的75条建议  | 旧一篇: java开发环境配置

    评论:没有评论。

    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © 黑蜘蛛