Hibernate中的继承映射

UNION-SUBCLASS继承映射

在UNION-SUBCLASS中,每一个继承子表对应一个实体类,这种继承关系重点在于程序的维护上,而数据表中依然会有重复数据。
【定义数据库脚本】

DROP TABLE IF EXISTS student;
DROP TABLE IF EXISTS worker;

CREATE TABLE student(
  id       VARCHAR(50),
  name     VARCHAR(50),
  age      INT,
  school   VARCHAR(50),
  score    DOUBLE,
  CONSTRAINT pk_sid PRIMARY KEY(id)
);

CREATE TABLE worker(
  id       VARCHAR(50),
  name     VARCHAR(50),
  age      INT,
  company  VARCHAR(50),
  salary   DOUBLE,
  CONSTRAINT pk_wid PRIMARY KEY(id)
);

通过观察以上的数据库脚本,发现他们具有重复的字段:id、name、age。但是如果按照传统的思路,现在应该准备出两个类
但是发现此时程序出现了冗余,因此,我们应设计一个抽象类来存储公共的属性。

基于.hbm.xml文件实现

  1. 定义一个Member.java的抽象类,设置公共属性
package com.gub.vo;

import java.io.Serializable;

@SuppressWarnings("serial")
public abstract class Member implements Serializable {
    private String id;
    private String name;
    private Integer age;
    //settter、getter略
}

随后使Student和Worker类继承Member类

package com.gub.vo;

import java.io.Serializable;

@SuppressWarnings("serial")
public class Student extends Member implements Serializable {
    private String school;
    private Double score;
    //settter、getter略
}
package com.gub.vo;

import java.io.Serializable;

@SuppressWarnings("serial")
public class Worker extends Member implements Serializable {
    private String company;
    private Double salary;
   //settter、getter略
}

此时的代码已经符合面向对象的设计要求了,重复的属性可以在子类中通过继承关系继续使用。
2. 编写映射文件
继承关系有一个重要的特点 ,所有子类对象都可以向父类对象转换,所以再进行操作的过程之中,对于映射文件,只要求提供一个Member.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="com.gub.vo.Member"  schema="company">
        <id name="id" column="id"/>
        <property name="name" column="name"/>
        <property name="age" column="age"/>
        <!--表示学生信息-->
        <union-subclass name="com.gub.vo.Student" table="student" catalog="company">
            <property name="school" column="school"/>
            <property name="score" column="score"/>
        </union-subclass>
        <!--表示工人信息-->
        <union-subclass name="com.gub.vo.Worker" table="worker" catalog="company">
            <property name="company" column="company"/>
            <property name="salary" column="salary"/>
        </union-subclass>
    </class>
</hibernate-mapping>
  1. 测试学生增加
package test;

import com.gub.dbc.HibernateSessionFactory;
import com.gub.vo.Student;

public class StudentInsertTest {
    public static void main(String[] args) {
        Student student = new Student();
        student.setId("GUB");
        student.setName("NicholasGUB");
        student.setAge(20);
        student.setSchool("xx学校");
        student.setScore(62.31);
        HibernateSessionFactory.getSession().save(student);
        HibernateSessionFactory.getSession().beginTransaction().commit();
        HibernateSessionFactory.closeSession();
    }
}

发现数据保存成功,并且通过观察SQL语句 Hibernate: insert into company.student (name, age, school, score, id) values (?, ?, ?, ?, ?) 发现如果保存的是学生信息(使用的是学生这个子类),那么SQL语句将向Student表中保存(同理,如果换为工人信息,SQL语句将向Worker表中保存)。

在使用子类映射的过程之中,虽然只有一个映射文件,但是会根据子类的不同找到不同部分的数据表进行操作。

  1. 测试查询学生信息
package test;

import com.gub.dbc.HibernateSessionFactory;
import com.gub.vo.Student;

public class StudentQueryTest {
    public static void main(String[] args) {
        Student student = (Student)HibernateSessionFactory.getSession().get(Student.class,"GUB");
        System.out.println(student);
        HibernateSessionFactory.closeSession();
    }
}

此时发现数据查询成功,我们尝试用Member类进行数据的查询。
Student{school=‘xx学校’, score=62.31}

  1. 测试Member查询信息
package test;

import com.gub.dbc.HibernateSessionFactory;
import com.gub.vo.Member;
import com.gub.vo.Student;

public class StudentQueryTest {
    public static void main(String[] args) {
        Member member = (Student)HibernateSessionFactory.getSession().get(Member.class,"GUB");
        System.out.println(member);
        HibernateSessionFactory.closeSession();
    }
}

ERROR: Table ‘company.member’ doesn’t exist
由于此时没有Member数据表,所以查询出现错误,那么也说明,在进行“UNION-SUBCALSS”映射的过程中,父类的查询无法使用。

基于Annotation的配置

  1. 定义Member.java类
import javax.persistence.*;
import java.io.Serializable;

@SuppressWarnings("serial")
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Member implements Serializable {
    private String id;
    private String name;
    private Integer age;

    @Id
    @Column(name = "id")
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Basic
    @Column(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Basic
    @Column(name = "age")
    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Member{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  1. 定义Student子类和Worker子类
import javax.persistence.*;
import java.io.Serializable;

@SuppressWarnings("serial")
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Student extends Member implements Serializable {
    private String school;
    private Double score;

    @Basic
    @Column(name = "school")
    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    @Basic
    @Column(name = "score")
    public Double getScore() {
        return score;
    }

    public void setScore(Double score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return super.toString()+"Student{" +
                "school='" + school + '\'' +
                ", score=" + score +
                '}';
    }
}
import javax.persistence.*;
import java.io.Serializable;

@SuppressWarnings("serial")
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Worker extends Member implements Serializable {
    private String company;
    private Double salary;

    @Basic
    @Column(name = "company")
    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    @Basic
    @Column(name = "salary")
    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return super.toString()+"Worker{" +
                "company='" + company + '\'' +
                ", salary=" + salary +
                '}';
    }
}

此时相当于一共提供了三个实体对象,那么在hibernate.cfg.xml文件里面就必须定义有三个实体的映射。

  1. 修改hibernate.cfg.xm文件,添加以下映射
        <mapping class="com.gub.vo.Member" />
        <mapping class="com.gub.vo.Student"/>
        <mapping class="com.gub.vo.Worker"/>
  1. 用上例的测试代码进行测试
    信息增加成功
    Hibernate: insert into Student (age, name, school, score, id) values (?, ?, ?, ?, ?)
    5.再次测试Member查询信息:
    发现数据查询成功:
    Member{id=‘GUB’, name=‘NicholasGUB’, age=20}Student{school=‘xx学校’, score=62.31}
Hibernate: 
    select
        member0_.id as id1_0_0_,
        member0_.age as age2_0_0_,
        member0_.name as name3_0_0_,
        member0_.school as school1_1_0_,
        member0_.score as score2_1_0_,
        member0_.company as company1_2_0_,
        member0_.salary as salary2_2_0_,
        member0_.clazz_ as clazz_0_ 
    from
        ( select
            id,
            age,
            name,
            school,
            score,
            null as company,
            null as salary,
            1 as clazz_ 
        from
            Student 
        union
        select
            id,
            age,
            name,
            null as school,
            null as score,
            company,
            salary,
            2 as clazz_ 
        from
            Worker 
    ) member0_ 
where
    member0_.id=?

通过观察执行的sql语句发现,此时是根据Member查询,而不是根据Student或Worker查询,因为不确定数据是在student表中还是在worker表中,所以现在的查询是两张表一起查询。如果直接查询Student(即明确子类),那么肯定不会再去关联其他查询结果。

JOINED继承映射

JOINED继承形式主要是解决了数据表上的重复问题
数据库脚本

DROP TABLE IF EXISTS student;
DROP TABLE IF EXISTS worker;
DROP TABLE IF EXISTS people;

CREATE TABLE people(
  pid    VARCHAR(50),
  name     VARCHAR(50),
  age      INT,
  CONSTRAINT pk_peo PRIMARY KEY(pid)
);

CREATE TABLE student(
  pid    VARCHAR(50),
  school   VARCHAR(50),
  score    DOUBLE,
  CONSTRAINT pk_stu PRIMARY KEY(pid),
  CONSTRAINT fk_stu FOREIGN KEY(pid) REFERENCES people(pid)
);

CREATE TABLE worker(
  pid    VARCHAR(50),
  company  VARCHAR(50),
  salary   DOUBLE,
  CONSTRAINT pk_wor PRIMARY KEY(pid),
  CONSTRAINT fk_wor FOREIGN KEY(pid) REFERENCES people(pid)
);

此时如果要实现继承映射除了类上的关系之外,表中也存在关系,但是如果要查询一个学生或者是一个工人的完整信息,需要两张表。
【基于hbm的配置】

  1. 定义People.java类
@SuppressWarnings("serial")
public abstract class People implements Serializable {
    private String pid;
    private String name;
    private Integer age;
    //方法省略
}
  1. 定义Student.java类和Worker.java类
package com.gub.vo;

import java.io.Serializable;

@SuppressWarnings("serial")
public class Student extends People implements Serializable {
    private String school;
    private Double score;
    //方法省略
}
package com.gub.vo;

import java.io.Serializable;

@SuppressWarnings("serial")
public class Worker extends People implements Serializable {
    private String company;
    private Double salary;
    //方法省略
}

此时与UNIONSUB-CLASS相比,只有表结构上有所不同,但是类的结构上区别不大,但是people表与student和worker表存在继承关联,那么配置文件也要进行相关的配置。

  1. 修改People.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="com.gub.vo.People" table="people" schema="company">
        <id name="pid" column="pid"/>
        <property name="name" column="name"/>
        <property name="age" column="age"/>
        <!--表示学生信息-->
        <joined-subclass name="com.gub.vo.Student" table="student" catalog="company">
            <key>
                <column name="pid" length="50" />
            </key>
            <property name="school" column="school" />
            <property name="score" column="score" />
        </joined-subclass>
        <!--表示工人信息-->
        <joined-subclass name="com.gub.vo.Worker" table="worker" catalog="company">
            <key>
                <column name="pid" length="50" />
            </key>
            <property name="company" column="company" />
            <property name="salary" column="salary" />
        </joined-subclass>
    </class>
</hibernate-mapping>

此时的代码结构之中已经明确描述除了关联列。使用上例的增加程序测试增加,发现数据增加成功,观察控制台输出。

由于现在student表是people表的子表(student表占着people表的外键),所以people表的数据要先进行增加,而后在增加student表的信息。
再使用上例代码测试查询,发现查询成功,观察控制台输出:

发现此时进行连接时使用了“inner join”,数据库的连接形式只有两种:内连接(产生笛卡尔积)、外连接。因此此时的数据查询性能不好。如果直接查询成员信息,发现同样性能很差。
【基于Annotation的配置】
JPA标准本身支持了Annotation的配置操作模式,其基本形式与UNIONSUB-CLASS形式类似

  1. 定义People.java类
package com.gub.vo;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;

@SuppressWarnings("serial")
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class People implements Serializable {
   private String pid;
   private String name;
   private Integer age;
   //方法省略
}
  1. 定义Student.java类和Worker.java类
package com.gub.vo;

import javax.persistence.*;
import java.io.Serializable;

@SuppressWarnings("serial")
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Student extends People implements Serializable {
    private String school;
    private Double score;
    //方法省略
}
package com.gub.vo;

import javax.persistence.*;
import java.io.Serializable;

@SuppressWarnings("serial")
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Worker extends People implements Serializable {
  private String company;
  private Double salary;
  //方法省略 
}

运行测试方法,发现与hbm.xml文件配置的结果一样。

SUBCLASS继承映射(识别器)

在以上的两种继承映射之中都会发现如下的缺点:

  • UNION-SUBCLASS继承映射只在类结构上实现了继承及结构的通用,但是在数据表操作上依然会出现重复的字段,所以设计上不好。
  • JOINED继承映射在数据表的设计上产生了继承关系,可是结果发现在数据查询时存在有多表查询的操作关系。

SUBCLASS(识别器)将具备有类继承关系的结构定义在一张数据表中,随后使用一个字段作为区分符。
数据库脚本如下:

DROP TABLE IF EXISTS people;

CREATE TABLE people(
 pid    VARCHAR(50),
 name     VARCHAR(50),
 age      INT,
 school   VARCHAR(50),
 score    DOUBLE,
 company  VARCHAR(50),
 salary   DOUBLE,
 type  VARCHAR(50),
 CONSTRAINT pk_peo PRIMARY KEY(pid)
);

观察上表发现出现了一个type的字段,而这个字段主要是描述数据表的作用:

  • type=‘student’:pid、name、age、school、score存在数据,其他数据为空。
  • type=‘worker’:pid、name、age、company、salary存在数据,其他数据为空。

也就是说利用字段的不同取值来决定所使用的字段有哪些,描述出这些概念。

【基于hbm.xml的配置】

  1. 定义People.java类(必须考虑type字段的问题,因为此字段的内容要交给Hibernate)
package com.gub.vo;

import java.io.Serializable;

@SuppressWarnings("serial")
public class People implements Serializable {
    private String pid;
    private String name;
    private Integer age;
    //方法省略
}
  1. 定义Student.java类和Worker.java类
package com.gub.vo;

import java.io.Serializable;

public class Student extends People implements Serializable {
    private String school;
    private Double score;
    //方法省略
}
package com.gub.vo;

import java.io.Serializable;

public class Worker extends People implements Serializable {
    private String company;
    private Double salary;
}
  1. 修改People.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="com.gub.vo.People" table="people" schema="company">
        <id name="pid" column="pid"/>
        <!--需要由Hibernate自己维护type字段的内容-->
        <discriminator type="java.lang.String">
            <column name="type" />
        </discriminator>
        <property name="name" column="name"/>
        <property name="age" column="age"/>
        <subclass name="com.gub.vo.Student" discriminator-value="student" >
            <property name="school" column="school"/>
            <property name="score" column="score"/>
        </subclass>
        <subclass name="com.gub.vo.Worker" discriminator-value="worker">
            <property name="company" column="company"/>
            <property name="salary" column="salary"/>
        </subclass>
    </class>
</hibernate-mapping>

【测试增加数据】

观察控制台输出发现,type的内容自动设置为了“student”(discriminator-value=“student”)。
【测试查询数据】

此时在查找学生信息的时候,会自动设置type为“student"的信息。
【基于Annotation的配置】

  1. 定义People.java类
package com.gub.vo;

import javax.persistence.*;
import java.io.Serializable;

@SuppressWarnings("serial")
@Entity
@DiscriminatorColumn(name = "type",discriminatorType = DiscriminatorType.STRING)
@Table(name = "people",catalog = "company")
public class People implements Serializable {
   private String pid;
   private String name;
   private Integer age;
   //方法略
}
  1. 新建Student.java类和Worker.java类
package com.gub.vo;

import javax.persistence.*;
import java.io.Serializable;

@SuppressWarnings("serial")
@Entity
@DiscriminatorValue(value="student")
public class Student extends People implements Serializable {
   private String school;
   private Double score;
   //方法略
}
package com.gub.vo;

import javax.persistence.*;
import java.io.Serializable;

@SuppressWarnings("serial")
@Entity
@DiscriminatorValue(value="worker")
public class Worker extends People implements Serializable {
   private String company;
   private Double salary;
    //方法略
}

识别器的列由父类决定,那么识别器的内容由子类设置。
3. 用上面的测试代码进行测试,发现执行结果与hbm.xml文件的配置一致。

通过以上三种继承映射发现,类的继承关系一直保持不变,唯一改变的只是表的结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值