上篇文章我们对持久化对象进行的学习,了解了它的三种不同的状态并通过它完成对数据库的映射操作。但这都是基于单张表的操作,如果两张或者两张以上的表之间存在某种关联,我们又该如何利用持久化对象进行操作呢?本篇主要介绍的关联映射就是针对有着某种关联的多张表的各种操作,主要涉及内容如下:
- 组合主键的映射
- 组件的映射
- 单向多对一的映射
- 单向一对多的映射
- 双向一对多的映射
- 级联映射
一、组合主键的映射操作
根据我们的上篇文章,对于单一主键,在对象映射配置文件中使用 id标签即可完成配置。但是,往往有些主键并不是单一的,它可能由多个字段组合,那么此时就不能使用 id标签进行指定了。例如,我们有一张scores表,该表有三个字段,uid表示学生id,sub表示学生考试的学科,score表示该门考试成绩。那么确定一个学生的某门考试成绩就需要uid和usub,此时它们就是scores表的主键,因为它能唯一确定一行数据。
/*定义scores实体类*/
public class Scores {
private Score scoreId; //主键
private int score;
//省略get,set方法
}
public class Score implements Serializable {
private int userId;
private String sub;
//省略get,set方法
}
在这里,我们定义scores实体类,我们将主键封装成一个类score,该类必须继承接口Serializable ,最好还能实现它的两个方法equals和hashcode。然后就是我们的实体映射配置文件的编写:
<class name="DbClasses.Scores" table="scores">
<composite-id name="scoreId" class="DbClasses.Score">
<key-property name="userId" column="userId"></key-property>
<key-property name="sub" column="sub"></key-property>
</composite-id>
<property name="score" column="score"></property>
</class>
对于组合主键,我们使用标签composite-id来配置,name和class属性分别指定主键类在实体类中的名称及其位置。该标签下的key-property标签则是用来指定主键成员对应于数据表中的具体字段的。我们运行程序,看看Hibernate为我们创建的表中是否有一个组合主键:
显然,在我们的scores表中,userId和sub的组合构成了该表的主键。这就是组合主键在Hibernate中的配置情况,组合主键还是比较常见的。
二、组件映射
这里将要介绍的组件映射和上述介绍的主键映射名称相似,但确实完全不同的概念,需要予以区别。假设我们有一张person表:
person表中有主键id,name,age字段,还有三个地址字段(往往一个人有多个地址,我们要分别进行保存)。但是这样的一个表结构对应于我们的实体类如下:
public class Person {
private int id;
private String name;
private int age;
private String address1;
private String address2;
private String address3;
//省略get,set方法
}
对于这样的实体类来说,我们觉得他对于地址字段的处理是冗余的,假如某个人有十个地址,难道要在我们的实体类中配置十个属性吗?显然是不合理的,Hibernate允许我们像主键映射一样将所有的地址字段抽象出来一个类。
public class Person {
private int id;
private String name;
private int age;
private Address address;
//省略get,set方法
}
public class Address {
private String address1;
private String address2;
private String address3;
//省略get,set方法
}
重点在于我们的实体映射文件的配置,
<class name="DbClasses.Person" table="person">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="name" column="name"></property>
<property name="age" column="age"></property>
<!--映射的一个组件属性-->
<component name="address" class="DbClasses.Address">
<property name="address1" column="address1"></property>
<property name="address2" column="address2"></property>
<property name="address3" column="address3"></property>
</component>
</class>
实体类中其他的属性照常配置,对于这个Address类型的属性,我们使用component标签进行配置,name和class分别指定组件名和其位置,在该标签下,使用property标签配置组件的成员对应于数据表中的字段。然后我们删除表,重新看看这次Hibernate为我们生成的表结构:
显然结果是一样的,我们使用组件映射的一个好处就在于在这个实体类中,对于数据表结构显得非常清晰,代码的封装性更好,方便查错。
三、单向多对一的映射
以上介绍的两种基本映射并不属于我们本篇将要介绍的关联映射,关联映射就是指在处理多张有关联的表时,我们的实体类的配置。所谓的多对一就是指,其中一张表的主键是另一张表的外键,例如:
我们有一张Student表,一张grade表,其中grade表的主键id是Student表的外键(grade),Student中的多条记录对应于grade的一条记录,所以这种表的关联又被称作多对一的关联关系。下面我们看看如何通过对实体类的配置达到构建这种多对一的数据表关联。
public class Student {
private int uId;
private String name;
private int age;
private Grade grade;
//省略get,set方法
}
public class Grade {
private int id;
private String grade;
//省略get,set方法
}
Student和Grade分别对应不同数据库表的两个实体类,但是Student实体类中有一个属性grade指向实体类Grade。实体类映射配置文件如下:
<class name="DbClasses2.Student" table="student">
<id name="uId" column="uid">
<generator class="native"></generator>
</id>
<property name="name"></property>
<property name="age"></property>
<many-to-one name="grade" class="DbClasses2.Grade" column="grade_id"></many-to-one>
</class>
<class name="DbClasses2.Grade" table="grade">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="grade" column="grade"></property>
</class>
Grade实体类的配置没什么变化,Student中使用many-to-one标签将本实体类中属性grade配置指向另一个实体类Grade,并用column指定外键名称。也就是当Hibernate根据映射配置文件创建数据表的时候,发现属性grade指向的是一个实体类Grade,于是把Grade表的主键关联到grade字段上。我们先运行程序看看HIbernate是否为我们创建了这种外键关联,然后通过插入数据进一步理解Hibernate在底层为我们做的事情。
显然,在分别创建Student和Grade表之后,Hibernate又向数据库发送了一条alter语句,该语句负责添加外键关联。下面我们看看能否利用外键获取到Grade表中的成绩。
/*首先向表中插入信息*/
Student student = new Student();
student.setName("single");
student.setAge(21);
Grade grade = new Grade();
grade.setGrade("优秀");
student.setGrade(grade);
session.save(grade);
session.save(student);
我们知道,一个实体类的对象对应于数据表的一条记录,那么grade代表Grade表的一条记录,而该对象作为属性值被赋值给Student中的grade属性则表示它将自己的引用交给了Student的外键字段,也就是说student这条记录可以通过外键字段找到grade代表的这条记录。有点绕,但是学过数据库原理的应该不难理解。下面我们看,如何利用外键获取对应的Grade表中的一条完整记录。
Student student = (Student)session.get(Student.class,1);
Grade grade = student.getGrade();
System.out.println(grade.getId()+":"+grade.getGrade());
输出结果:
1:优秀
显然,我们通过Student返回的grade对象代表的就是基于Student外键字段值在Grade表中的一条数据。
四、单向一对多的映射
单向many-to-one关联是最常见的单向关联关系,其逻辑也趋近与我们的Sql语言,还算比较好理解。而对于单向一对多的映射则是其的一个逆向的逻辑,相对而言比较难以理解。这个多对一和一对多之间有个很明显的区别,对于多对一的情况,我们在得到Student对象代表的一条数据记录时,可以利用外键得到相对应Grade表中的一条记录。但是反过来,如果我们想知道对于Grade表的某条记录究竟有多少Student表记录予以对应呢?起码这是多对一无法直接解决的,那么我们的一对多则着重解决的就是这么一个问题。
所谓的一对多就是利用一的一方完成这种外键关联的构建。我们先看实体类的定义:
/*student实体类的定义*/