Hibernate—— 一对多 和 多对多关联关系映射(xml和注解)总结(转载)
Posted xoxobool
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hibernate—— 一对多 和 多对多关联关系映射(xml和注解)总结(转载)相关的知识,希望对你有一定的参考价值。
-
One to Many 映射关系
- 多对一单向外键关联(XML/Annotation)
- 一对多单向外键关联(XML/Annotation)
- 懒加载和积极加载
- 一对多双向外键关联(XML/Annotation)
-
Many to Many 映射关系
- 多对多单向外键关联(XML/Annotation)
- 多对多双向外键关联(XML/Annotation)
- set的inverse元素详解
- 问题小结
- 关联关系的优缺点
多对一单向外键关联关系
注意多对一关联是多方持有一方的引用。看一个例子,去淘宝购物,那么一个淘宝用户可以对应多个购物订单,如图所示:
多的一方是Orders,持有一方的引用,也就是Users,而在Users中无需作任何定义,从订单到用户的关系是单向多对一关联。对应数据库就是:
还有比如说学生和班级的关系,多个学生可以属于同一个班级,这就是从学生到班级也是典型的单向多对一关系,看代码实现:
基于注解的多对一单向外键关联:
单向多对一关联中,多方需要持有一方的引用,那么多方(学生类)需要额外配置,需要对持有的一方引用使用注解@ManyToOne (cascade={CascadeType.ALL}, fetch=FetchType.EAGER),设置为级联操作和饥渴的抓取策略,@JoinColumn(name="cid"),而一方(教室类)无需做任何多方的定义。
注意;多方必须保留一个不带参数的构造器!
复制代码 import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; //班级类,在多对一关系中属于一的方,不持有其他多余的配置,反而是被多方持有 @Entity public class ClassRoom { private int cid;//班级编号 private String cname;//班级名称 // 自动增长的主键 @Id @GeneratedValue public int getCid() { return cid; } public void setCid(int cid) { this.cid = cid; } public String getCname() { return cname; } public void setCname(String cname) { this.cname = cname; } }
一方——班级类无需做多余的定义,下面是多方——学生实体和配置:
import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; //学生实体类,属于多对一的多方,持有班级(一方)的引用 @Entity public class Students { private int sid; //编号 private String sname; //姓名 private ClassRoom classroom;//学生班级 //注意:多方一定要显式的定义不带参数的构造方法 public Students() { } public Students(String sname) { this.sname = sname; } // 多方使用注解:@ManyToOne // fetch=FetchType.EAGER,急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载。 // 全部级联操作,referencedColumnName显式设置数据库字段名cid,不写默认就是和name一样的。 @ManyToOne (cascade={CascadeType.ALL}, fetch=FetchType.EAGER) @JoinColumn(name="cid",referencedColumnName="cid") public ClassRoom getClassroom() { return classroom; } public void setClassroom(ClassRoom classroom) { this.classroom = classroom; } // 自动增长主键 @Id @GeneratedValue public int getSid() { return sid; } public void setSid(int sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } }
下面测试:先生成数据库脚本,再进行学生对象的插入
public class TestStudentsByAnno { private static SessionFactory sessionFactory; @Before public void setUp() throws Exception { System.out.println("setUp()..."); sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory(); } @After public void tearDown() throws Exception { System.out.println("tearDown()..."); sessionFactory.close(); } @Test public void testSave() { Session session = sessionFactory.getCurrentSession(); Transaction tx = session.beginTransaction(); try { ClassRoom c = new ClassRoom(); c.setCname("computer001"); Students s = new Students("zhangsan"); s.setClassroom(c); session.save(s); tx.commit(); } catch(Exception ex) { ex.printStackTrace(); tx.rollback(); } } @Test @Ignore public void testSchemaExport() { SchemaExport se = new SchemaExport(new AnnotationConfiguration().configure()); se.create(true, true); } }
反向创建表的数据库脚本如下:
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))插入一个学生对象,会自动生成如下语句:
ClassRoom c = new ClassRoom(); c.setCname("computer001"); Students s = new Students("zhangsan"); s.setClassroom(c); session.save(s); tx.commit();
Hibernate: insert into ClassRoom (cname) values (?)
Hibernate: insert into Students (cid, sname) values (?, ?)
插入成功:
基于xml配置实现多对一单向外键关联
<hibernate-mapping> <class name="net.nw.vo.fk.mto.ClassRoom" table="classroom"> <id name="cid" column="cid" type="int"> <generator class="native"/> </id> <property name="cname" column="cname" type="string"/> </class> </hibernate-mapping>
一方(教室类)无需做任何多方的定义。只需要维护好自己的属性配置即可。而多方只需要加上<many-to-one name="" column=“"/>就ok。
<hibernate-mapping> <class name="net.nw.vo.fk.mto.Students" table="students"> <id name="sid" column="sid" type="int"> <generator class="native"/> </id> <property name="sname" column="sname" type="string"/> <many-to-one name="classroom" column="cid"/> </class> </hibernate-mapping>
hibernate.cfg.xml里加上
<mapping resource="net/nw/vo/fk/mto/ClassRoom.hbm.xml" /> <mapping resource="net/nw/vo/fk/mto/Students.hbm.xml" />
注意:如果没有设置级联ALL,那么需要在保存的时候先保存班级,在保存学生,否则出错: object references an unsaved transient instance - save the transient instance before flushing:
ClassRoom classRoom = new ClassRoom(); classRoom.setCname("CS"); Students students = new Students("111"); students.setClassroom(classRoom); session.save(classRoom); session.save(students); tx.commit();
小结:使用<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=""),除了级联之外,还要设置一方为懒加载模式。且外键还是加在了多方学生表里,只不过控制权变了,之前多对一关联是多方学生持有班级外键,控制班级,现在一对多关联,表里还是多方学生持有班级一方的外键,只不过控制权交给了班级,让班级控制学生。不要混淆。
import javax.persistence.*; import java.util.Set; //班级类是一方,一方持有多方的引用 @Entity public class ClassRoom { private int cid;//班级编号 private String cname;//班级名称 private Set<Students> stus ;//班级的学生集合是多方 // 现在是一方维护多方了,主控权交给了一方,设置级联,一方要设置懒加载,推荐! @OneToMany(cascade={CascadeType.ALL},fetch=FetchType.LAZY) @JoinColumn(name="cid") // 设置一方的外键,这里是cid,因为实际上这个外键还是加在多方,只不过控制权变了。 public Set<Students> getStus() { return stus; } public void setStus(Set<Students> stus) { this.stus = stus; } @Id @GeneratedValue public int getCid() { return cid; } public void setCid(int cid) { this.cid = cid; } public String getCname() { return cname; } public void setCname(String cname) { this.cname = cname; } }
注意,不论多对一还是一对多,多方都要显式保留无参构造器。
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; //学生实体类 @Entity public class Students { private int sid; //编号 private String sname; //姓名 //注意:一定要保留这个默认不带参数的构造方法 public Students(){ } public Students(String sname) { this.sname = sname; } @Id @GeneratedValue public int getSid() { return sid; } public void setSid(int sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } }
执行数据库脚本,发现一方(主控方)还是和之前多对一的表结构一样,多方也是如此。
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))执行测试,保存学生,因为现在关系是一方维护,控制多方。肯定保存主控方——班级(和之前相反,之前多对一保存的是多方学生对象),但是本质上还是先保存的学生班级,再自动保存学生,这点和多对一本质一样。
Set<Students> stus = new HashSet<>(); stus.add(new Students("zhangsan")); stus.add(new Students("lisi")); stus.add(new Students("wangwu")); stus.add(new Students("zhaoliu")); stus.add(new Students("sunqi")); ClassRoom c = new ClassRoom(); c.setCname("cs001"); c.setStus(stus); session.save(c); tx.commit();
生成的脚本如下:先插入外键的班级对象,在执行五个学生的插入操作,最后执行五个更新,为sid=1。。。5的学生,更新cid为2
Hibernate: insert into ClassRoom (cname) values (?) Hibernate: insert into Students (sname) values (?) Hibernate: insert into Students (sname) values (?) Hibernate: insert into Students (sname) values (?) Hibernate: insert into Students (sname) values (?) Hibernate: insert into Students (sname) values (?) Hibernate: update Students set cid=? where sid=? Hibernate: update Students set cid=? where sid=? Hibernate: update Students set cid=? where sid=? Hibernate: update Students set cid=? where sid=? Hibernate: update Students set cid=? where sid=?
总结:多对一时候,多方设置EAGER,一方设置LAZY,也就是说,如果是多对一,多方控制一方,那么多方设置积极加载,一方无需多余配置,反过来,如果是一对多关系,一方控制多方,那么一方设置懒加载,多方无需多余配置,但是不论哪种,多方都显式加上一个不带参数的构造器。
一对多里的懒加载
记得之前总结,get和load的查询方式源码的时候,就总结了一下懒加载load里的应用,之前说Hibernate中,当访问的数据量过大时,用缓存也不太合适, 因为内存容量有限 ,为了减少并发量,减少系统资源的消耗,Hibernate用懒加载机制来弥补这种缺陷,但是这只是弥补而不是用了懒加载总体性能就提高了。懒加载也被称为延迟加载,它在查询的时候不会立刻访问数据库,而是返回代理对象,比如之前总结的load方式查询,当真正去使用对象的时候才会访问数据库。除了load查询默认使用懒加载,现在我们的一对多关联映射也使用了lazy加载,下面进行实际验证测试:
public void testQuery() { Session session = sessionFactory.getCurrentSession(); Transaction tx = session.beginTransaction(); try { // 首先查询班级,cid=1的班级 ClassRoom c =(ClassRoom) session.get(ClassRoom.class, 1); // 通过班级导航到学生,遍历学生得到名字 for(Students s : c.getStus()) { System.out.println("姓名 :" + s.getSname()); } tx.commit(); } catch(Exception ex) { ex.printStackTrace(); tx.rollback(); } }
执行之后,debug发现:在没有使用班级对象的时候,只有这样一条SQL语句:从classroom表查询,cid=1的班级,使用使用AS赋给列一个别名。
Hibernate: select classroom0_.cid as cid0_0_, classroom0_.cname as cname0_0_ from ClassRoom classroom0_ where classroom0_.cid=?
等执行到for了,才打印这一语句:
Hibernate: select stus0_.cid as cid0_1_, stus0_.sid as sid1_, stus0_.sid as sid1_0_, stus0_.sname as sname1_0_ from Students stus0_ where stus0_.cid=?
充分说明这是执行的懒加载模式。一方控制多方,一方设置懒加载,如果什么都不设置,会是什么情况?经过验证,发现和显式设置懒加载效果一样,也就是说,one-to-many(元素)的懒加载是默认的,这是必须的,是常用的策略。一对多的时候,查询主对象时默认是懒加载。即:查询主对象的时候不会把从对象查询出来,使用从对象的时候才加载从对象。
如果人为设置为积极加载,则直接全部查询,@OneToMany(cascade={CascadeType.ALL},fetch=FetchType.EAGER),SQL语句为如下,进行了外连接的查询。一次性查了主对象和从对象出来。
Hibernate: select classroom0_.cid as cid0_1_, classroom0_.cname as cname0_1_, stus1_.cid as cid0_3_, stus1_.sid as sid3_, stus1_.sid as sid1_0_, stus1_.sname as sname1_0_ from ClassRoom classroom0_ left outer join Students stus1_ on classroom0_.cid=stus1_.cid where classroom0_.cid=?
基于xml文件配置:
一方作为主控方:
<set name="" > <key column=""/> <one-to-many class= "" /> </set>
一方是班级,持有多方的集合,如下配置:
<hibernate-mapping> <class name="net.nw.vo.fk.otm.ClassRoom" table="classroom"> <id name="cid" column="cid" type="int"> <generator class="native"/> </id> <property name="cname" column="cname" type="string"/> <set name="stus" > <!-- 外键还是班级cid --> <key column="cid"/> <one-to-many class="net.nw.vo.fk.otm.Students" /> </set> </class> </hibernate-mapping>
多方是学生,作为从对象,别忘了,保留无参构造器,如下配置:
<hibernate-mapping> <class name="net.nw.vo.fk.otm.Students" table="students"> <id name="sid" column="sid" type="int"> <generator class="native"/> </id> <property name="sname" column="sname" type="string"/> </class> </hibernate-mapping>
一对多双向外键关联
其实类似之前的一对一双向外键关联,也是互相持有对方的引用,故也叫双向一对多自身关联。多方持有一方的引用,@ManyToOne(cascade={CascadeType.ALL}),@JoinColumn(name="")。反过来,一方也持有多方的集合,@OneToMany(cascade={CascadeType.ALL}),@JoinColumn(name="")。代码如下:
基于注解的配置:
import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; //学生实体类,属于多方,持有一方的引用 @Entity public class Students { private int sid; //编号 private String sname; //姓名 private ClassRoom classroom;//学生班级属于一方 //注意:一定要在多方保留这个默认不带参数的构造方法 public Students() { } public Students(String sname) { this.sname = sname; } // 多方是设置积极加载,全部级联 @ManyToOne (cascade={CascadeType.ALL}, fetch=FetchType.EAGER) @JoinColumn(name="cid",referencedColumnName="cid") // 外键设置为班级id,cid public ClassRoom getClassroom() { return classroom; } public void setClassroom(ClassRoom classroom) { this.classroom = classroom; } @Id @GeneratedValue public int getSid() { return sid; } public void setSid(int sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } }
关键是一方,也必须持有多方的集合,形成你中有我,我中有你的局面,互相控制。但是还是注意,本质上,数据库表里外键cid还是加在了学生表——多方的表里。
import javax.persistence.*; import java.util.Set; //班级类 @Entity public class ClassRoom { private int cid;//班级编号 private String cname;//班级名称 private Set<Students> stus; // 一方也持有了多方:学生的集合引用 // 一方也要控制多方,一方设置懒加载,外键还是cid,也就是外键还是加在多方——学生表。 @OneToMany(cascade={CascadeType.ALL},fetch=FetchType.LAZY) @JoinColumn(name="cid") public Set<Students> getStus() { return stus; } public void setStus(Set<Students> stus) { this.stus = stus; } @Id @GeneratedValue public int getCid() { return cid; } public void setCid(int cid) { this.cid = cid; } public String getCname() { return cname; } public void setCname(String cname) { this.cname = cname; } }
测试脚本生成。和之前表一样,只不过控制权双方都有了:
alter table Students drop foreign key FK73AC29B8559B6D03 drop table if exists ClassRoom drop table if exists Students 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)) alter table Students add index FK73AC29B8559B6D03 (cid), add constraint FK73AC29B8559B6D03 foreign key (cid) references ClassRoom (cid)
此时先保存谁都可以!控制权是双方都有。
基于xml配置:
多方:<many-to-one name="group" column="gid"></many-to-one>,一方:记住,一方是持有集合<set name="" > <key column=""></key> <one-to-many class=""/> </set>
本例代码如下:
<hibernate-mapping> <class name="net.nw.vo.bfk.mto.Students" table="students"> <id name="sid" column="sid" type="int"> <generator class="native"/> </id> <property name="sname" column="sname" type="string"/> <many-to-one name="classroom" column="cid"/> </class> </hibernate-mapping> ---------------------------------------------------------------------------------- <hibernate-mapping> <class name="net.nw.vo.bfk.mto.ClassRoom" table="classroom"> <id name="cid" column="cid" type="int"> <generator class="native"/> </id> <property name="cname" column="cname" type="string"/> <set name="stus" > <key column="cid"/> <one-to-many class="net.nw.vo.bfk.mto.Students" /> </set> </class> </hibernate-mapping>
小结:在关系模型中,只存在外键参照关系,而且是many方参照one方。
多对多的关联关系映射
现在有一个角色类,和一个特权类,前者保存了都有哪些人(角色)拥有哪些特权,后者保存的是一些特权,比如可以做什么,不可以做什么等让哪些人拥有。如图类关系:
这就是一个多对多的例子,他们之间在数据库如何实现的关联呢?显然不能互相持有对方主键做外键,那么就需要用到一个新的表——映射表作为中间表:
再举一个最熟悉的学生的例子,现实中,学生和教师就构成了多对多的关联关系。一个教师可以教很多学生,同时一个学生可以师从很多老师,拿这个例子说明。
多对多单向外键关联
其中一个多方持有另一个多方的集合对象,而且前面也分析了,还需要创建一个中间表,先看两个实体对象配置基于注解的多对多单向外键关系配置:一个多方持有另一个多方的集合对象,我让学生持有老师的集合对象引用,同样的另一个多方——老师不做多余配置,且多方要显式设置一个无参构造器,那么学生需要使用注解@ManyToMany和@JoinTable,设置级联全部cascade=CascadeType.ALL,代码如下:import javax.persistence.*; import java.util.Set; //学生实体类 @Entity public class Students { private int sid; //编号 private String sname; //姓名 private Set<Teachers> teachers ; // 我设置学生这个多方去持有老师这个多方的集合,去控制老师 //注意:一定要保留这个默认不带参数的构造方法 public Students() { } public Students(String sname) { this.sname = sname; } // 先设置多对多的关联,之后必须生成一个中间表,使用JoinTable注解 @ManyToMany(cascade=CascadeType.ALL) @JoinTable( // 设置中间表名 name="teachers_students", // 指定当前对象的外键,本表在中间表的外键名称 joinColumns={@JoinColumn(name="sid")}, // 指定关联对象的外键,另一个表在中间表的外键名称。 inverseJoinColumns={@JoinColumn(name="tid")} ) public Set<Teachers> getTeachers() { return teachers; } public void setTeachers(Set<Teachers> teachers) { this.teachers = teachers; } @Id @GeneratedValue public int getSid() { return sid; } public void setSid(int sid) { this.sid = sid; } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } }
另一个多方,老师不做多余配置:
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Teachers { private int tid;//教师的编号 private String tname;//教师姓名 public Teachers() { } public Teachers(String tname) { this.tname = tname; } @Id @GeneratedValue public int getTid() { return tid; } public void setTid(int tid) { this.tid = tid; } public String getTname() { return tname; } public void setTname(String tname) { this.tname = tname; } }
生成的数据库脚本如下:
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都是作为了中间表的联合主键,他们同时也是外键:
下面进行插入数据的测试,因为学生持有老师集合引用,且设置了级联,故直接保存学生就ok:// 因为学生持有教师的集合,先设置教师 Set<Teachers> teachers = new HashSet<>(); teachers.add(new Teachers("Wang")); teachers.add(new Teachers("Li")); teachers.add(new Teachers("Song")); teachers.add(new Teachers("Zhang")); Students s = new Students(); s.setSname("zhangsan"); s.setTeachers(teachers); session.save(s); tx.commit();
基于xml的多对多单向外键关系配置:
学生这个多方持有老师的集合,那么持有对方集合的学生映射文件配置如下:
<hibernate-mapping> <class name="net.nw.vo.fk.mtm.Students" table="students"> <id name="sid" column="sid" type="int"> <generator class="native"/> </id> <property name="sname" column="sname" type="string"/> <!-- 学生表持有老师的集合,如下进行配置 --> <set name="teachers" table="students_teachers" cascade="all"> <!-- table设置中间表,级联是all --> <!-- key设置本对象在中间表的外键sid --> <key column="sid"/> <!-- many-to-many 标签设置对方的表(老师)在中间表的外键tid --> <many-to-many class= "net.nw.vo.fk.mtm.Teachers" column="tid"/> </set> </class> </hibernate-mapping>
老师表配置就简单了:
<hibernate-mapping> <class name="net.nw.vo.fk.mtm.Teachers" table="teachers"> <id name="tid" column="tid" type="int"> <generator class="native"/> </id> <property name="tname" column="tname" type="string"/> </class> </hibernate-mapping>
进行测试(删除之前的表,先删除中间表,在删除老师表,最后删除学生表):
Set<Teachers> teachers = new HashSet<>(); teachers.add(new Teachers("Teacher Wang")); teachers.add(new Teachers("Teacher Li")); teachers.add(new Teachers("Teacher Song")); teachers.add(new Teachers("Teacher Zhang")); Students s = new Students(); s.setSname("zhangsan"); s.setTeachers(teachers); session.save(s); tx.commit();
多对多双向外键关联
和之前的类似,是互相持有对方的集合,双方持有对方的集合对象,其中一方设置@ManyToMany(mappedBy=""),另一方:
@ManyToMany @JoinTable( name="", joinColumns={@JoinColumn(name="")}, inverseJoinColumns={@JoinColumn(name="")} )
基于注解的配置,看具体代码:
import javax.persistence.*; import java.util.Set; //学生实体类 @Entity public class Students { private int sid; //编号 private String sname; //姓名 private Set<Teachers> teachers ; //注意:一定要保留这个默认不带参数的构造方法 public Students() { } public Students(String sname) { this.sname = sname; } @ManyToMany(cascade=CascadeType.ALL) @JoinTable( name="teachers_students", joinColumns={@JoinColumn(name="sid")}, inverseJoinColumns={@JoinColumn(name="tid")} ) public Set<Teachers> getTeachers() { return teachers; } public void setTeachers(Set<Teachers> teachers) { this.teachers = teachers; } @Id @GeneratedValue public int getSid() { return sid; } public void setSid(int sid) { this.sid = sid; } public String getSname() { <
以上是关于Hibernate—— 一对多 和 多对多关联关系映射(xml和注解)总结(转载)的主要内容,如果未能解决你的问题,请参考以下文章
hibernate映射的 关联关系:有 一对多关联关系,一对一关联关系,多对多关联关系,继承关系
Hibernate,关系映射的多对一单向关联多对一双向关联一对一主键关联一对一外键关联多对多关系关联