天天看点

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的知识点总结如下:

one to many 映射关系

多对一单向外键关联(xml/annotation)

一对多单向外键关联(xml/annotation)

懒加载和积极加载

一对多双向外键关联(xml/annotation)

many to many 映射关系

多对多单向外键关联(xml/annotation)

多对多双向外键关联(xml/annotation)

set的inverse元素详解

问题小结

关联关系的优缺点

  多对一单向外键关联关系

  注意多对一关联是多方持有一方的引用。看一个例子,去淘宝购物,那么一个淘宝用户可以对应多个购物订单,如图所示:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  多的一方是orders,持有一方的引用,也就是users,而在users中无需作任何定义,从订单到用户的关系是单向多对一关联。对应数据库就是:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  还有比如说学生和班级的关系,多个学生可以属于同一个班级,这就是从学生到班级也是典型的单向多对一关系,看代码实现:

  

  基于注解的多对一单向外键关联:

  单向多对一关联中,多方需要持有一方的引用,那么多方(学生类)需要额外配置,需要对持有的一方引用使用注解@manytoone (cascade={cascadetype.all}, fetch=fetchtype.eager),设置为级联操作和饥渴的抓取策略,@joincolumn(name="cid"),而一方(教室类)无需做任何多方的定义。

  注意;多方必须保留一个不带参数的构造器!

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

一方——班级类无需做多余的定义,下面是多方——学生实体和配置:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

下面测试:先生成数据库脚本,再进行学生对象的插入

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

反向创建表的数据库脚本如下:

create table classroom (cid integer not null auto_increment, cname varchar(255), primary key (cid))

create table students (sid integer not null auto_increment, sname varchar(255), cid integer, primary key (sid))

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

插入一个学生对象,会自动生成如下语句:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

hibernate: insert into classroom (cname) values (?)

hibernate: insert into students (cid, sname) values (?, ?)

插入成功:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  基于xml配置实现多对一单向外键关联

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  一方(教室类)无需做任何多方的定义。只需要维护好自己的属性配置即可。而多方只需要加上<many-to-one name="" column=“"/>就ok。

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  hibernate.cfg.xml里加上

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

注意:如果没有设置级联all,那么需要在保存的时候先保存班级,在保存学生,否则出错: object references an unsaved transient instance - save the transient instance before flushing: 

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  小结:使用<many-to-one>元素进行多对一关联关系配置,name属性  指定类的属性名,column属性 指定库表字段名,class属性  指定类属性类型(加上姓,即包名),not-null属性 指定属性是否允许为空,cascade属性 指定是否级联保存和更新:save-update、delete、all、none。

  一对多单向外键关联

  当类与类建立了关联,程序能很方便的从一个对象导航到另一个或一组与之关联的对象,有了student对象,就可以通过student对象得到这个学生所属的班级的信息——students.getclassroom();,对于班级对象,如果想要得到某个学生的信息,怎么办呢?这时候可以反过来控制,一方控制多方,下面进行一对多单向外键关联。

  简单说就是和之前多对一相反,之前是多方持有一方的引用,而一对多关联关系是一方持有多方的集合的引用,注意区别:这里是持有多方的集合。

  基于注解的配置:

   @onetomany(cascade={cascadetype.all},fetch=fetchtype.lazy),@joincolumn(name=""),除了级联之外,还要设置一方为懒加载模式。且外键还是加在了多方学生表里,只不过控制权变了,之前多对一关联是多方学生持有班级外键,控制班级,现在一对多关联,表里还是多方学生持有班级一方的外键,只不过控制权交给了班级,让班级控制学生。不要混淆。

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  注意,不论多对一还是一对多,多方都要显式保留无参构造器。

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  执行数据库脚本,发现一方(主控方)还是和之前多对一的表结构一样,多方也是如此。

  执行测试,保存学生,因为现在关系是一方维护,控制多方。肯定保存主控方——班级(和之前相反,之前多对一保存的是多方学生对象),但是本质上还是先保存的学生班级,再自动保存学生,这点和多对一本质一样。

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  生成的脚本如下:先插入外键的班级对象,在执行五个学生的插入操作,最后执行五个更新,为sid=1。。。5的学生,更新cid为2

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  总结:多对一时候,多方设置eager,一方设置lazy,也就是说,如果是多对一,多方控制一方,那么多方设置积极加载,一方无需多余配置,反过来,如果是一对多关系,一方控制多方,那么一方设置懒加载,多方无需多余配置,但是不论哪种,多方都显式加上一个不带参数的构造器。

  一对多里的懒加载

  记得之前总结,get和load的查询方式源码的时候,就总结了一下懒加载load里的应用,之前说hibernate中,当访问的数据量过大时,用缓存也不太合适, 因为内存容量有限 ,为了减少并发量,减少系统资源的消耗,hibernate用懒加载机制来弥补这种缺陷,但是这只是弥补而不是用了懒加载总体性能就提高了。懒加载也被称为延迟加载,它在查询的时候不会立刻访问数据库,而是返回代理对象,比如之前总结的load方式查询,当真正去使用对象的时候才会访问数据库。除了load查询默认使用懒加载,现在我们的一对多关联映射也使用了lazy加载,下面进行实际验证测试:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  执行之后,debug发现:在没有使用班级对象的时候,只有这样一条sql语句:从classroom表查询,cid=1的班级,使用使用as赋给列一个别名。

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  等执行到for了,才打印这一语句:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  充分说明这是执行的懒加载模式。一方控制多方,一方设置懒加载,如果什么都不设置,会是什么情况?经过验证,发现和显式设置懒加载效果一样,也就是说,one-to-many(元素)的懒加载是默认的,这是必须的,是常用的策略。一对多的时候,查询主对象时默认是懒加载。即:查询主对象的时候不会把从对象查询出来,使用从对象的时候才加载从对象。

  如果人为设置为积极加载,则直接全部查询,@onetomany(cascade={cascadetype.all},fetch=fetchtype.eager),sql语句为如下,进行了外连接的查询。一次性查了主对象和从对象出来。

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  基于xml文件配置:

  一方作为主控方:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  一方是班级,持有多方的集合,如下配置:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  多方是学生,作为从对象,别忘了,保留无参构造器,如下配置:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  一对多双向外键关联

  其实类似之前的一对一双向外键关联,也是互相持有对方的引用,故也叫双向一对多自身关联。多方持有一方的引用,@manytoone(cascade={cascadetype.all}),@joincolumn(name="")。反过来,一方也持有多方的集合,@onetomany(cascade={cascadetype.all}),@joincolumn(name="")。代码如下:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  关键是一方,也必须持有多方的集合,形成你中有我,我中有你的局面,互相控制。但是还是注意,本质上,数据库表里外键cid还是加在了学生表——多方的表里。

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  测试脚本生成。和之前表一样,只不过控制权双方都有了:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

   此时先保存谁都可以!控制权是双方都有。

  基于xml配置:

  多方:<many-to-one name="group" column="gid"></many-to-one>,一方:记住,一方是持有集合

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  本例代码如下:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  小结:在关系模型中,只存在外键参照关系,而且是many方参照one方。

  多对多的关联关系映射

  现在有一个角色类,和一个特权类,前者保存了都有哪些人(角色)拥有哪些特权,后者保存的是一些特权,比如可以做什么,不可以做什么等让哪些人拥有。如图类关系:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  这就是一个多对多的例子,他们之间在数据库如何实现的关联呢?显然不能互相持有对方主键做外键,那么就需要用到一个新的表——映射表作为中间表:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  再举一个最熟悉的学生的例子,现实中,学生和教师就构成了多对多的关联关系。一个教师可以教很多学生,同时一个学生可以师从很多老师,拿这个例子说明。

  多对多单向外键关联

  其中一个多方持有另一个多方的集合对象,而且前面也分析了,还需要创建一个中间表,先看两个实体对象配置

  基于注解的多对多单向外键关系配置:

  一个多方持有另一个多方的集合对象,我让学生持有老师的集合对象引用,同样的另一个多方——老师不做多余配置,且多方要显式设置一个无参构造器,那么学生需要使用注解@manytomany和@jointable,设置级联全部cascade=cascadetype.all,代码如下:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  另一个多方,老师不做多余配置:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  生成的数据库脚本如下:

create table students (sid integer not null auto_increment, sname varchar(255), primary key (sid))

create table teachers (tid integer not null auto_increment, tname varchar(255), primary key (tid))

create table teachers_students (sid integer not null, tid integer not null, primary key (sid, tid))

  主要关注中间表,sid和tid都是作为了中间表的联合主键,他们同时也是外键:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  下面进行插入数据的测试,因为学生持有老师集合引用,且设置了级联,故直接保存学生就ok:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  基于xml的多对多单向外键关系配置:

  学生这个多方持有老师的集合,那么持有对方集合的学生映射文件配置如下: 

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  老师表配置就简单了:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  进行测试(删除之前的表,先删除中间表,在删除老师表,最后删除学生表):

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  多对多双向外键关联

  和之前的类似,是互相持有对方的集合,双方持有对方的集合对象,其中一方设置@manytomany(mappedby=""),另一方:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  基于注解的配置,看具体代码:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  关键是另一方的配置,前面总结了,双向关联不会真的是互相维持,只能交给一方去维护:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  生成数据库脚本:(和之前的单向多对多一样的表结构,关键看实体中控制权的变化)

  基于xml的配置:

  其中一方:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  另一方:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  具体代码:

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结
Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

  注意:set元素配置;

  属性name  指定类的属性名,table指定多对多关联关系中间表,cascade 级联操作属性:save-update、delete、all、none,一般all就ok,lazy属性可以指定是否是懒加载。set的子元素key元素——设定本表在中间表的外键名称。

    inverse属性设置:

  inverse是hibernate中双向关联关系中的基本概念,在xml配置里,用来设置关系由哪一方来维护,inverse=true 表示被控方,false表示主控方,在多对一,一对一关联关系中,hibernate默认设置多方的inverse=true,即多方为被控方,一方的inverse=false,即一方为主控方。在多对多关系中需要我们自己设置哪一方为被控方即设置inverse=true。上述例子没有设置,其实需要手动设置的,对应注解里的mappedby属性。

  关联关系的优缺点

  使用关联关系,就可以直接操作内存中的对象,不用每次都查询数据库,会提高效率;而且域模型真实反映了客观世界的关系,但是缺点就是建立复杂的关联关系会给程序开发带来麻烦,当修改一个对象时,会牵连其它的对象,这也是为什么很多人说什么hibernate不好用……其实就是没学好,一般人掌握的不扎实,总出错,后来mybatis横空出现,大家就去用它了,比起hibernate来,上手非常简单,使用原生sql……本质不是一个真正的orm架构模式的实现框架。当然mybatis也有它的优点和缺点,以后再总结它。

  一句话:具体要建立对象之间的什么关联关系要根据具体的需求。具体使用什么框架,要听领导的,哈哈。、

  问题小结

注意在多对一/一对多关系里:多方必须保留一个不带参数的构造器!

如果没有设置级联all,那么需要在保存的时候先保存班级,在保存学生,否则出错: object references an unsaved transient instance - save the transient instance before flushing: 

多对一时候,多方设置eager加载,一对多的时候,一方设置lazy加载

多对多关联,多方需要保留一个无参构造器。

辛苦的劳动,转载请注明出处,谢谢……