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文件实现
- 定义一个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>
- 测试学生增加
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表中保存)。
在使用子类映射的过程之中,虽然只有一个映射文件,但是会根据子类的不同找到不同部分的数据表进行操作。
- 测试查询学生信息
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}
- 测试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的配置
- 定义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 +
'}';
}
}
- 定义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文件里面就必须定义有三个实体的映射。
- 修改hibernate.cfg.xm文件,添加以下映射
<mapping class="com.gub.vo.Member" />
<mapping class="com.gub.vo.Student"/>
<mapping class="com.gub.vo.Worker"/>
- 用上例的测试代码进行测试
信息增加成功
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的配置】
- 定义People.java类
@SuppressWarnings("serial")
public abstract class People implements Serializable {
private String pid;
private String name;
private Integer age;
//方法省略
}
- 定义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表存在继承关联,那么配置文件也要进行相关的配置。
- 修改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形式类似
- 定义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;
//方法省略
}
- 定义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的配置】
- 定义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;
//方法省略
}
- 定义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;
}
- 修改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的配置】
- 定义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;
//方法略
}
- 新建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文件的配置一致。
通过以上三种继承映射发现,类的继承关系一直保持不变,唯一改变的只是表的结构。