上篇文章我们通过注解对映射了单个实体类,但是具体项目中往往实体类之间又是相互关联的,本篇文章就是从实体类之间存在的不同关联角度,具体学习下如何映射他们之间的关联,主要涉及内容如下:

  • 单向的一对一关联关系映射
  • 单向的多对一的关联关系映射
  • 单向的一对多的关联关系映射
  • 单向的多对多的关联关系映射
  • 双向的一对一关联关系映射
  • 双向的一对多关联关系映射
  • 双向的多对多关联关系映射

一、单向的一对一关联关系映射

首先,我们需要知道什么样的两张表具有一对一的关联关系。

这就是一个典型的单向的一对一的关联关系,所谓的一对一其实就是指,主表中的一条记录唯一的对应于从表中的一条记录。但具体到我们的实体类中又该如何来写呢?我们先看一个完整映射代码,然后逐渐解释其中的相关注解。

//定义实体类userinfo
@Entity
@Table(name = "userinfo")
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_id;
private String name;
private int age; @OneToOne(targetEntity = UserCode.class)
@JoinColumn(name = "code",referencedColumnName = "code_id",unique = true)
private UserCode userCode;
//省略getter,setter方法
}
//定义实体类usercode
@Entity
@Table(name = "code")
public class UserCode {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int code_id;
private String code;
//省略getter,setter方法
}

因为是单向的一对一,所以我们的usercode表并不存在外键列可以直接访问到userinfo表,所以它的实体类配置没什么特殊的地方。而userinfo实体类定义了一个UserCode 类型的属性,当我们使用hibernate进行插入或者返回数据时候,usercode表中对应的记录则会被装在在这个属性中,当然,我们也通过它配置外键关联关系。

@OneToOne注解指定这是一个一对一的关联关系,targetEntity 指定了被关联的实体类类型。@JoinColumn用于配置外键列,name属性用于指定外键列的列名,Hibernate将会在userinfo表中增加一个字段用做外键列。referencedColumnName 属性用于指定该外键列用于参照的表字段,这里我们参照的是usercode表的主键。由于是一对一,所以要求外键列不能重复,指定unique唯一约束即可。

对比着表中的各个字段,再次体会下上述注解中的属性的各个值的意义。

二、单向的多对一的关联关系映射

依然,在详细学习之前,先看看什么样的两张表构成多对一的关系。

像这种,userinfo表中多条不同的记录对应于usersex表中的一条记录的情况,我们称作多对一的关联关系。其中,多的一方设有外键列,掌控着关系的维护。看代码:

//定义usersex实体类
@Entity
@Table(name = "userSex")
public class UserSex {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int sex_id;
private String sex;
//省略getter,setter方法
}
//定义userinfo实体类
@Entity
@Table(name = "userinfo")
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int uid;
private String name;
private int age; @ManyToOne(targetEntity = UserSex.class)
@JoinColumn(name = "sex",referencedColumnName = "sex_id")
private UserSex userSex;
//省略getter,setter方法
}

同样,@ManyToOne指定这是个多对一关系,并通过targetEntity 属性指定被关联的实体类型。@JoinColumn依然用于配置外键列。

对比着表中的各个字段,再次体会下上述注解中的属性的各个值的意义。

三、单向的一对多的关联关系映射

单向的一对多和单向的多对一是完全不同的两种表间关系。虽然两张表看起来是没什么太大差别,但是关系的维护方确实截然相反的。这种情况下,两张表的关系则由一的一方进行维护,所以在一的一端需要定义一个集合属性用于映射多的一端的记录集合,看代码:

//定义一的一端的实体类
@Entity
@Table(name = "userSex")
public class UserSex {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int sex_id;
private String sex; @OneToMany(targetEntity = UserInfo.class)
@JoinColumn(name = "sex",referencedColumnName = "sex_id")
private Set users;
//省略getter,setter方法
}
//定义多的一端的实体类
@Entity
@Table(name = "userinfo")
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int uid;
private String name;
private int age;
//省略getter,setter方法
}

其中,@OneToMany指定了两个表之间的是一种一对多的关联关系,targetEntity 属性指定被关联的实体类类型。这里的@JoinColumn是不一样的,它将生成一个外键字段,但不是生成在本实体类所代表的数据表中,而是生成在被关联的数据表中。name属性指定了外键字段的字段名称,referencedColumnName属性指定了该外键字段的值依赖于本表的那个字段(我们这里让他依赖于userSex的主键)。

实际上一对多就是多对一的一个逆向的关联关系,但是两张表依然是通过一个外键列来维系,只不过这个外键列由谁生成的有点不同。具体的表结构此处不再贴出,我们通过插入数据来感受下一对多的关联关系表。

UserInfo user1 = new UserInfo("a",1);
UserInfo user2 = new UserInfo("b",55);
UserInfo user3 = new UserInfo("c",4);
UserInfo user4 = new UserInfo("d",3); Set<UserInfo> sets = new HashSet<UserInfo>();
sets.add(user1);
sets.add(user2);
sets.add(user3);
sets.add(user4); UserSex userSex = new UserSex();
userSex.setSex("男");
userSex.setUsers(sets); session.save(user1);
session.save(user2);
session.save(user3);
session.save(user4); session.save(userSex);

当我们执行上述程序的时候,hibernate首先会为我们插入四条userinfo记录到userinfo表中(其中的外键字段为空),然后插入一条记录到usersex表中,在这之后,hibernate将根据set集合中的元素依次执行这么一条SQL语句:

update userinfo set sex=? where uid=?

显然,根据集合中每个元素的id值定位userinfo表,并将这些元素的外键字段同一赋值为当前usersex实例的主键值。这样两张表就形成了对应的关系了。当然,当我们想要取出一条usersex实例时候,hibernate也会拿该实例的主键值去搜索userinfo表,并将匹配的记录装载到set集合中。

不过这种由一的一端管理关联关系的情况有点反常规逻辑,因此不建议用一的一端管理整个关联关系。

四、单向的多对多的关联关系映射

对于单向的多对多关联关系,我们无法使用外键列进行管理。所以,一般会增设一张辅助表来维系两张表之间的关联关系,举个例子:一个人可以有多个兴趣爱好,一个兴趣爱好也可以对应多个人,我可以获取到某个人所有兴趣爱好,也可以获取具有相同兴趣爱好的所有人。如果仅仅使用两张表来描述这种关联关系的话,根本就无法描述,不信你可以试试,即便可以实现,那种表结构也是极其复杂冗余的。目前最好的策略是引入第三方表来维系两张表之间的多对多关联。

这样的表结构是清晰的,也是易于维护的。下面看看代码实现:

//定义hobby实体类
@Entity
@Table(name = "hobby")
public class Hobby {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int hobby_id;
private String hobby;
//省略getter,setter方法
}
//定义实体类userinfo
@Entity
@Table(name = "userinfo")
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_id;
private String name;
private int age; @ManyToMany(targetEntity = Hobby.class)
@JoinTable(name = "connectTable",
joinColumns = @JoinColumn(name = "user_id",referencedColumnName = "user_id"),
inverseJoinColumns = @JoinColumn(name = "hobbyid",referencedColumnName = "hobbyid")
)
private Set sets;
//省略getter,setter方法
}

多对多的关联映射需要使用到一个注解@JoinTable,该注解用于指定新生成的连接表的相关信息。name 属性指定表名,joinColumns 配置外键列及其依赖的属性字段,我们这里在新表中指定一列名为user_id并且依赖于userinfo实体的主键字段的值,inverseJoinColumns 用于指定关联的实体类的外键列,我们这里在新表中会生成一列名hobbyid并依赖Hobby实体类的主键值。

当我们插入数据的时候,会首先分别插入两张表的记录,然后会根据userinfo表中的集合属性中的元素向连接表中进行插入。返回数据也是类似的。

五、双向的一对一的关联关系映射

其实本质上看,单向的关联关系和双向的关联关系的区别在于,单向的关系中,只有一方存在对另一方的引用,也就是可以通过外键列指向另一方,而被引用的一方并不具备指向别人的外键列,所以整个关系都只有一方在维护。而双向的关系则是两方都具备关系维护的能力,能够相互访问。我们看看实体类的映射:

//定义userinfo实体类
@Entity
@Table(name = "userinfo")
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_id;
private String name;
private int age; @OneToOne(targetEntity = UserCode.class)
@JoinColumn(name = "code",referencedColumnName = "code_id",unique = true)
private UserCode userCode;
//省略getter,setter方法
}
//定义usercode实体类
@Entity
@Table(name = "code")
public class UserCode {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int code_id;
private String code; @OneToOne(targetEntity = UserInfo.class,mappedBy = "userCode")
private UserInfo userInfo;
//省略getter,setter方法
}

映射双向一对一关系的时候,需要在两端都使用@OneToOne修饰,我们在userinfo端增加了一个外键列并指向usercode的主键。往往两张表只要有一方维护着关系就行了,不建议两方同时维护着关系,那样会造成性能上的损失,我们指定mappedBy 属性的值来告诉Hibernate,usercode端不打算维护关系。当我们指定了双向的关联关系之后,两方都存在对方的引用了,实现了互访的能力。

有人可能会有疑问,usercode一端放弃对关系的管理没有设置外键列,那么我们是如何通过usercode获得userinfo的引用呢?答案是:

//从usercode查到userinfo表的引用
UserCode userCode = (UserCode) session.get(UserCode.class,1);

hibernate通过左连接将根据外键列的值和usercode表的主键值连接了两张表,于是我们可以通过usercode的主键一次性查到两张表对应的记录,最后为我们返回相应的实例。

而如果想要通过userinfo表查询到usercode表的引用相对容易些,因为userinfo表中有一个外键列可以使用。查两次表即可。

六、双向的一对多的关联关系映射

其实双向的一对多和双向的多对一是同一种关联关系,只是主导关系的人不一样而已。看代码:

//定义userinfo实体类
@Entity
@Table(name = "userinfo")
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int uid;
private String name;
private int age; @ManyToOne(targetEntity = UserSex.class)
@JoinColumn(name = "sex_id",referencedColumnName = "sex_id",nullable = false)
private UserSex userSex;
//省略getter,setter方法
}
//定义usersex实体类
@Entity
@Table(name = "userSex")
public class UserSex {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int sex_id;
private String sex; @OneToMany(targetEntity = UserInfo.class,mappedBy = "userSex")
private Set users;
//省略getter,setter方法
}

一的一端使用@OneToMany修饰并放弃对关系的维护,多的一端使用@ManyToOne修饰,并增加外键列指向usersex表的主键列。其实和我们介绍的单向多对一基本一样,只是此处的一的一端增加了一个一对多的映射,增加了对userinfo表的一个引用而已。

对于我们从多的一端访问一的一端直接利用的外键列进行访问,从一的一端对多的一端的访问具体会生成以下两条SQL语句:

先根据usersex的主键值查一次usersex表,再通过usersex的主键值去查一次userinfo表,获取的所有的userinfo记录都会被注入到usersex的集合属性中。

七、双向的多对多的关联关系映射

双向的多对多关系关联的映射依然需要通过第三张辅助表来进行连接。依然使用我们上述的userinfo和hobby举例:

//定义实体类userinfo
@Entity
@Table(name = "userinfo")
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_id;
private String name;
private int age; @ManyToMany(targetEntity = Hobby.class)
@JoinTable(name = "connectTable",
joinColumns = @JoinColumn(name = "user_id",referencedColumnName = "user_id"),
inverseJoinColumns = @JoinColumn(name = "hobby_id",referencedColumnName = "hobby_id")
)
private Set sets = new HashSet();
//省略getter,setter方法
}
//定义实体类hobby
@Entity
@Table(name = "hobby")
public class Hobby {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int hobby_id;
private String hobby; @ManyToMany(targetEntity = UserInfo.class)
@JoinTable(name = "connectTable",
joinColumns = @JoinColumn(name = "hobby_id",referencedColumnName = "hobby_id"),
inverseJoinColumns = @JoinColumn(name = "user_id",referencedColumnName = "user_id")
)
private Set sets = new HashSet();
//省略getter,setter方法
}

多对多的两端都需要指定连接表的信息,但配置的是同一张表的信息,基本没什么变化。这些注解也是我们介绍过的,此处不再赘述。比如我们想要获取一个userinfo实例,那么hibernate会先根据指定的主键值查一次userinfo表,然后当需要用到usersex表的相关信息的时候,hibernate会拿userinfo的主键值再去查一次connect连接表,并将查到的usersex实例集注入userinfo的集合属性中。

综上,我们介绍了关系型数据库中常见的几种关联关系,并介绍了Hibernate是如何利用注解对实体类进行映射的。总的来说,单向的关联关系和双向的关联关系有一个最本质的区别,具有双向关联关系的两张表,各自都存在对对方的引用,也就是说可以互相访问的。而单向的关联关系则永远只有一方可以访问到另一方。

当读者在实际的项目开发中使用到这些关联关系的时候,想必对于Hibernate的映射操作会有更加深刻的认识。总结不到之处,望指出!

Hibernate框架学习之注解配置关系映射的更多相关文章

  1. Hibernate框架学习之注解映射实体类

         前面的相关文章中,我们已经介绍了使用XML配置文件映射实体类及其各种类型的属性的相关知识.然而不论是时代的潮流还是臃肿繁杂的配置代码告诉我们,注解配置才是更人性化的设计,于是学习了基本的映射 ...

  2. Spring框架学习之注解配置与AOP思想

         上篇我们介绍了Spring中有关高级依赖关系配置的内容,也可以调用任意方法的返回值作为属性注入的值,它解决了Spring配置文件的动态性不足的缺点.而本篇,我们将介绍Spring的又一大核心 ...

  3. Hibernate框架之双向多对多关系映射

    昨天跟大家分享了Hibernate中单向的一对多.单向多对一.双向一对多的映射关系,今天跟大家分享下在Hibernate中双向的多对多的映射关系 这次我们以项目和员工举个栗子,因为大家可以想象得到,在 ...

  4. Hibernate框架学习(七)——多对多关系

    一.关系表达 1.表中的表达 2.实体中的表达 3.orm元数据中的表达 在User.hbm.xml中添加: 在Role.hbm.xml中添加(与上相反): 二.操作关联属性 1.保存员工及角色 pu ...

  5. JavaEE之Hibernate(开放源代码的对象关系映射框架)

    Hibernate(开放源代码的对象关系映射框架) 1.简介 Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全 ...

  6. MyBatis 3(中文版) 第四章 使用注解配置SQL映射器

    本章将涵盖以下话题: l 在映射器Mapper接口上使用注解 l 映射语句 @Insert,@Update,@Delete,@SeelctStatements l 结果映射 一对一映射 一对多映射 l ...

  7. Android数据库框架——ORMLite轻量级的对象关系映射(ORM)Java包

    Android数据库框架--ORMLite轻量级的对象关系映射(ORM)Java包 事实上,我想写数据库的念头已经很久了,在之前写了一个答题系统的小项目那只是初步的带了一下数据库,数据库是比较强大的, ...

  8. 用户、角色、权限三者多对多用hibernate的一对多注解配置

    用户.角色.权限三者多对多用hibernate的一对多注解配置 //权限表@Table(name = "p")public class P { @Id @GeneratedValu ...

  9. Android数据库框架——GreenDao轻量级的对象关系映射框架,永久告别sqlite

    Android数据库框架--GreenDao轻量级的对象关系映射框架,永久告别sqlite 前不久,我在写了ORMLite这个框架的博文 Android数据库框架--ORMLite轻量级的对象关系映射 ...

随机推荐

  1. Elixir游戏服设计六

    接上章,我新建了个app做包含Table模型, TableServer等.Table桌子的代码暂时如下, 有一些状态还没用上 defmodule Table do @state_accept 0 #准 ...

  2. javascript中call()、apply()、bind()的用法终于理解

    其实是一个很简单的东西,认真看十分钟就从一脸懵B 到完全 理解! 先看明白下面: 例1 obj.objAge;  //17 obj.myFun()  //小张年龄undefined 例2 shows( ...

  3. php 守护进程类

    最近个人项目中需要后台运行任务,之前一直是用nouhp & + 重定向输出 来后台跑任务,后来觉得不好维护原始数据,同时可能也没有直接操作进程那么稳吧(没验证).废话少说,来看分析. 首先,我 ...

  4. Numpy的小总结

    1.Numpy是什么? numpy是Python的一个科学计算库,提供矩阵运算的功能. 1.1Numpy的导入 import numpy as np #一般都是用numpy的别名来进行操作 1.2Nu ...

  5. MyBatis 笔记总结

    1.MyBatis中的一些要点: 1.1 SqlMapConfig.xml:mybatis的全局配置文件,配置mybatis的运行环境等信息,包括mapper.xml文件 1.2 mapper.xml ...

  6. Java基础-数组(06)

    数组是存储多个变量(元素)的东西(容器),这多个变量的数据类型要一致 数组概念 数组是存储同一种数据类型多个元素的容器.数组既可以存储基本数据类型,也可以存储引用数据类型. 数组的定义格式 格式1:数 ...

  7. IntentService学习

    IntentService是一个Service,主要就是Service和HandlerThread的结合 一.使用 不用多说和使用Service差不多,但是比Service多个一个方法实现: publ ...

  8. 阿里巴巴Java开发手册思维导图

    趁着有时间把阿里巴巴Java开发手册又看了一遍了,很多时候觉得看完之后,发现自己好像一点都不记得了里面的内容了.只能把大概内容画一遍在脑子里形成一张图方便记忆,这样就更能够记得自己的看完的内容了.其中 ...

  9. java使用for循环做猜数字游戏

    package org.llh.test;import java.util.Random;import java.util.Scanner;/** * 猜数字游戏 *  * @author llh * ...

  10. 安装mayavi和VTK库的血泪史

    一开始安装VTK库是从官网上下载,但是怎么都找不到whl文件,只有exe文件(vtkpython-7.1.1-Windows-64bit.exe).下载安装之后再PyCharm中import vtk出 ...