上篇文章我们对持久化对象进行的学习,了解了它的三种不同的状态并通过它完成对数据库的映射操作。但这都是基于单张表的操作,如果两张或者两张以上的表之间存在某种关联,我们又该如何利用持久化对象进行操作呢?本篇主要介绍的关联映射就是针对有着某种关联的多张表的各种操作,主要涉及内容如下:

  • 组合主键的映射
  • 组件的映射
  • 单向多对一的映射
  • 单向一对多的映射
  • 双向一对多的映射
  • 级联映射

一、组合主键的映射操作

     根据我们的上篇文章,对于单一主键,在对象映射配置文件中使用 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实体类的定义*/
public class Student {
private int uId;
private String name;
private int age;
//省略get,set方法
}
public class Grade {
private int id;
private String grade;
//用于给其他表映射外键
private Set<Student> students = new HashSet<Student>(0);
//省略get,set方法
}

这里我们使用set集合,其实无论是list或者map都是可以的,旨在保存多的一方的记录。看似毫无关联的两张表却可以通过配置文件完成外键关联操作。有关Student实体的映射配置部分代码和平常是一样的,没有变动此处不再贴出,我们主要看Grade实体类的映射配置代码:

<class name="DbClasses2.Grade" table="grade">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="grade"/>
<set name="students">
<key column="grade_id"></key>
<one-to-many class="DbClasses2.Student"></one-to-many>
</set>
</class>

在Grade的实体类映射配置文件中,set标签用于配置属性students 。也就是说,当Hibernate加载到这里的时候,两张表单独创建完成之后,我要回到这里来,这里有一个一对多的外键需要更新,该外键的表载体在Student中,外键的名称是grade_id,于是它就会去更新Student的表结构,为它添加外键的引用,而引用的表就是Grade。这里还看不出set的作用,我们先看Hibernate为我们创建的表关联是否正确,然后通过存取数据来感受set的作用。

显然,Hibernate是先单独创建两张表,然后发送alter语句添加外键引用。那究竟set有什么用呢?它里面装的又是什么呢?

假设两张表中有如下信息:

下面我们通过程序获取成绩为优秀的所有Student。这一点在多对一映射中是做不到的。多对一只能知道某个学生的成绩是什么,但是无法直接知道成绩为什么的所有学生。

Grade grade = (Grade)session.get(Grade.class,1);
Set<Student> students = grade.getStudents();
//遍历输出所有学生姓名
Iterator<Student> iterator = students.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next().getName());
}

输出结果:

从Hibernate的日志输出中,我们可以很显然的看出来,首先Hibernate向数据库发送第一条select语句查询id为1的grade记录,然后默默的又一次发送select语句,不过这次是Student表,查询所有grade_id为1的记录并通过反射全部添加到set集合中。于是我们可以遍历输出所有Student信息。

五、双向一对多的映射

     双向一对多或者双向多对一都是一个意思,这种形式的关联映射操作就是上述的两种映射的结合,在多的一段配置多对一映射,在一的一段配置一对多映射。这样,我们既可以从多的一端通过外键获取到一的一端的详细记录又可以从一的一端通过自己的主键获取到多的一端的所有对应记录。这种方式比较简单,此处不再赘述,我们最后看看两个关键字cascade和inverse的含义和用法。

六、级联映射

     我们首先看级联操作,级联就是在两张具有关联关系的表操作的时候,通过操作其中一张表级联的更新了另一张表。先看个例子:

<many-to-one name="grade" class="DbClasses2.Grade" column="grade_id" cascade="save-update" />

我们在多的一端配置cascade等于save-update,意味着我们在多的一端进行save和update的时候,数据会自动更新到Grade表中。

Student stu1 = new Student();
stu1.setName("single");
stu1.setAge(21); Grade grade = new Grade();
grade.setGrade("优秀"); stu1.setGrade(grade); //session.save(grade);
session.save(stu1);

上述的这段代码完成的是一个插入操作,如果没有设置级联的话,该段程序必然报错,因为grade表中无任何数据,而student代表的一条记录的grade_id的字段却被强行插入数值1,自然会报错(外键1在grade表中找不到)。但是我们配置了级联就不一样了,Hibernate会先保存grade到数据库中,然后再插入student这条记录。从Hibernate的输出日志中也可以看出来:

当然,除了可以在多的一端配置级联,我们也可以在一的一端配置级联,让一的一端也可以级联的操作多的一端。例如:

<set name="students" cascade="save-update">
<key column="grade_id"></key>
<one-to-many class="DbClasses2.Student"></one-to-many>
</set>

给一的一端配置级联,然后我么通过级联来保存多的一端的数据。

Grade grade = new Grade();
grade.setGrade("优秀"); Student stu1 = new Student();
stu1.setName("single");
stu1.setAge(21); Student stu2 = new Student();
stu2.setName("cyy");
stu2.setAge(20); grade.getStudents().add(stu1);
grade.getStudents().add(stu2); //session.save(stu1);sesion.save(stu2);
session.save(grade);

这段代码在没有配置级联的状态下,必然会报错。首先Hibernate根据配置文件创建了两张表及其之间的关联关系。执行save的时候会将grade保存到数据表中,然后Hibernate会查看自己set集合中对应的多端的记录并根据这些记录去更新多端表中的外键值,当然如果没有保存到student表中,自然会报错。我们看看级联是怎么做的:

显然,在保存好grade之后,立马将自己set集合中的Student记录插入到Student表中,然后通过update语句更新他们的外键值。而没有设置级联的话,第二三条Sql语句是没有的,报错那也是自然的。关于级联,只要理解了它的本质,这些操作也都是可以理解的,本质上就是在做插入或者修改操作的时候如果发现自己代表的这条记录中有外部关联表的内容,那么则先完成对外部表的更新。这就是级联,级联的操作和自己关联的外部表,当然cascade也不止这一个参数值:

cascade="all|none|save-update|delete"

其中,none表示不级联,all表示所有操作都级联,save-update 表示保存和修改操作进行级联,delete表示删除的时候级联删除。本质都类似,此处不再赘述。

至此,有关关联映射的第一部分介绍完了,下篇将继续介绍未完的其他关联映射的操作。总结不到之处,望指出!

初识Hibernate之关联映射(一)的更多相关文章

  1. 初识Hibernate之关联映射(二)

    上篇我们介绍了关联映射的几种形式,有单向多对一,单向一对多,还有双向一对多.本篇接着介绍有关关联映射的其他几种映射方式,主要有以下几种: 基于外键的单向一对一关联映射 基于主键的单向一对一关联映射 单 ...

  2. Hibernate注解----关联映射注解以及课程总结详解----图片版本

    上一篇,记录了Hibernate注解----类级别注解以及属性注解详解 ,我们这一节主要讲解的是Hibernate注解----关联映射注解以及课程总结详解. 本节的主要内容: 第3章 关联映射注解 3 ...

  3. Hibernate的关联映射——双向1-N关联

    Hibernate的关联映射--双向1-N关联 对于1-N的关联,Hibernate推荐使用双向关联,而且不要让1的一端控制关联关系,而是用N的一端控制关联关系.双线的1-N关联和N-1关联是两种相同 ...

  4. Hibernate的关联映射——单向1-N关联

    Hibernate的关联映射--单向1-N关联 单向1-N关联的持久化类里需要使用集合属性.因为1的一端需要访问N的一端,而N的一端将以集合(Set)形式表现.从这个意义上来看,1-N(实际上还包括N ...

  5. Hibernate的关联映射——单向1-1关联

    Hibernate的关联映射--单向1-1关联 对于单向的1-1关联关系,需要在持久化类里增加代表关联实体的成员变量,并为该成员变量添加setter方法和getter方法.从持久化类的代码上看,单向1 ...

  6. Hibernate的关联映射——单向N-1关联

    Hibernate的关联映射--单向N-1关联 N-1是非常常见的关联关系,最常见的父子关系也是N-1关联,单向的N-1关联只需从N的一端可以访问1的一端. 为了让两个持久化类能够支持这种关联映射,程 ...

  7. Hibernate之关联映射(一对多和多对一映射,多对多映射)

    ~~~接着之前的Hibernate框架接着学习(上篇面试过后发现真的需要学习以下框架了,不然又被忽悠让去培训.)~~~ 1:Hibernate的关联映射,存在一对多和多对一映射,多对多映射: 1.1: ...

  8. Hibernate的关联映射关系

    一:多对一 <many-to-one 1.name:当前类的属性名(关联映射的类) 2.column:属性多对应的类的对应的表的外键(连接条件) 3.class:属性所对应的类的权限定名 4.n ...

  9. 关于Hibernate的关联映射

    何为关联映射 由于数据库的表与表之间存在的管理关系,可以分为一对一,一对多和多对多关联,一般情况下,在数据库设计中是通过表的外键来建立各种关系的,在Hibernate中则把数据库表与表之间的关系数据映 ...

随机推荐

  1. 【javascript】谈谈HTML5 ——HTML兽进化, H5兽!

    作为一名Web开发者,可能你并没有对这个“H5”这个字眼投入太多的关注,但实际上它早已不知不觉进入到你的开发中,并且总有一天会让你不得不正视它,了解它并运用它   打个比方:<海贼王>中的 ...

  2. ADO.NET中SQL Server数据库连接池

    连接到数据库服务器通常由几个需要很长时间的步骤组成. 必须建立物理通道(例如套接字或命名管道),必须与服务器进行初次握手,必须分析连接字符串信息,必须由服务器对连接进行身份验证,必须运行检查以便在当前 ...

  3. 前端面试angular 常问问题总结

    1. angular的数据绑定采用什么机制?详述原理 angularjs的双向数据绑定,采用脏检查(dirty-checking)机制.ng只有在指定事件触发后,才进入 $digest cycle : ...

  4. input[type="button"]与<button>的区别

    <button>标签  浏览器支持  所有主流浏览器都支持<button>标签.  重要事项:如果在HTML表单中使用button元素,不同的浏览器会提交不同的值.IE将提交& ...

  5. 【打死树莓派】-树莓派3代jessie+Opencv-解决安装不了libgtk2.0-dev包问题

    按照国际法先贴问题 Some packages could not be installed. This may mean that you have requested an impossible ...

  6. 这是一款借助chrome 插件的微信机器人

    1.chrome kit微信机器人简介 借助chrome 插件 js注入来实现消息的发送 chrome devtool api的调用来监听https请求 打开微信登录界面,在扫码登录前必须先打开too ...

  7. 父子一对多iframe,子iframe改子iframe元素

    $("iframe", parent.document).contents().find("#ProductNameIn").val(66666666); 1. ...

  8. [js高手之路]Node.js实现简易的爬虫-抓取博客文章列表信息

    抓取目标:就是我自己的博客:http://www.cnblogs.com/ghostwu/ 需要实现的功能: 抓取文章标题,超链接,文章摘要,发布时间 需要用到的库: node.js自带的http库 ...

  9. React——组件

    一.创建组件 在React中有两种创建组件的方式,分别是函数形式的组件和类形式的组件 //函数形式: function Welcome(props){ return <p>this is ...

  10. webpack配置这一篇就够

    最近看了一篇好文,根据这个文章重新梳理了一遍webpack打包过程,以前的一些问题也都清楚了,在这里分享一下,同时自己也做了一些小的调整 原文链接:http://www.jianshu.com/p/4 ...