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

  • 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() {
return sname;
} public void setSname(String sname) {
this.sname = sname;
}
}

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

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import java.util.Set; @Entity
public class Teachers {
private int tid;//教师的编号 private String tname;//教师姓名 private Set<Students> stus ; public Teachers() {
} public Teachers(String tname)
{
this.tname = tname;
} // 把控制权交给student类——teachers集合引用
@ManyToMany(mappedBy="teachers")
public Set<Students> getStus() {
return stus;
} public void setStus(Set<Students> stus) {
this.stus = stus;
} @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))

  基于xml的配置:

  其中一方:
<set name="teachers" table="students_teachers">

  <key column="sid"></key>

  <many-to-many class="net.nw.vo.Teachers" column="tid"/>

</set>

  另一方:

<set name="students" table="students_teachers">

  <key column="tid"></key>

  <many-to-many class="net.nw.vo.Students" column="sid"/>

</set>

  具体代码:

<hibernate-mapping>
<class name="net.nw.vo.bfk.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">
<key column="sid"/>
<many-to-many class= "net.nw.vo.bfk.mtm.Teachers" column="tid"/>
</set>
</class>
</hibernate-mapping> --------------------------------------------------------------------- <hibernate-mapping>
<class name="net.nw.vo.bfk.mtm.Teachers" table="teachers">
<id name="tid" column="tid" type="int">
<generator class="native"/>
</id> <property name="tname" column="tname" type="string"/> <set name="stus" table="students_teachers" cascade="all">
<key column="tid"/>
<many-to-many class= "net.nw.vo.bfk.mtm.Students" column="sid"/>
</set>
</class>
</hibernate-mapping>

  

  注意: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加载
  • 多对多关联,多方需要保留一个无参构造器。

欢迎关注

dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结的更多相关文章

  1. Hibernate—— 一对多 和 多对多关联关系映射(xml和注解)总结(转载)

    One to Many 映射关系 多对一单向外键关联(XML/Annotation) 一对多单向外键关联(XML/Annotation) 懒加载和积极加载 一对多双向外键关联(XML/Annotati ...

  2. Hibernate自身一对多和多对多关系映射

    一对多关系映射大家都明白,关系双方都一个含有对方多个引用,但自身一对多很多同学都不明白什么意思,那么首先我就说明一下什么是自身一对多,其实也很好理解,自身一对多就是自身含有本身的多个引用,例如新闻类别 ...

  3. hibernate 2 一对多、多对一 双向映射

    多对一或一对多中,在多的一方维护关系效率高 一:java实体类 1.Classes.java package cn.gs.ly.school.entity; import java.util.Set; ...

  4. Hibernate中一对多和多对一关系

    1.单向多对一和双向多对一的区别? 只需要从一方获取另一方的数据时 就使用单向关联双方都需要获取对方数据时 就使用双向关系 部门--人员 使用人员时如果只需要获取对应部门信息(user.getdept ...

  5. (转)Hibernate框架基础——多对多关联关系映射

    http://blog.csdn.net/yerenyuan_pku/article/details/52756536 多对多关联关系映射 多对多的实体关系模型也是很常见的,比如学生和课程的关系.一个 ...

  6. Java基础-SSM之mybatis一对多和多对一关系映射

    Java基础-SSM之mybatis一对多和多对一关系映射 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.准备测试环境(创建数据库表)  1>.创建customers表: ...

  7. mybatis多对多关联关系映射

    mybatis多对多关联关系映射 多对多关系在java类实体中表示为,一个类中包含了集合为另一个类的属性.而这连个实体都需要包含对方的集合类的属性. 例如:订单和商品,一个订单包含多个商品,一个商品又 ...

  8. Nhibernate 一对一,一对多,多对多 成功映射

    前语: 在Nhibernate xml 的文件配置上,一对一和多对多的配置比较简单,容易出错的反而是一对多(多对一)上. 1.一对一关联关系的映射: <one-to-one name=" ...

  9. Spring Boot 入门系列(二十八) JPA 的实体映射关系,一对一,一对多,多对多关系映射!

    前面讲了Spring Boot 使用 JPA,实现JPA 的增.删.改.查的功能,同时也介绍了JPA的一些查询,自定义SQL查询等使用.JPA使用非常简单,功能非常强大的ORM框架,无需任何数据访问层 ...

随机推荐

  1. 学习AOP之透过Spring的Ioc理解Advisor

    花了几天时间来学习Spring,突然明白一个问题,就是看书不能让人理解Spring,一方面要结合使用场景,另一方面要阅读源代码,这种方式理解起来事半功倍.那看书有什么用呢?主要还是扩展视野,毕竟书是别 ...

  2. ExtJS 4.2 组件介绍

    目录 1. 介绍 1.1 说明 1.2 组件分类 1.3 组件名称 1.4 组件结构 2. 组件的创建方式 2.1 Ext.create()创建 2.2 xtype创建 1. 介绍 1.1 说明 Ex ...

  3. 模拟AngularJS之依赖注入

    一.概述 AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的. 依赖注入,简而言之,就是解除硬编码,达到解偶的目的 ...

  4. 11、Struts2 的文件上传和下载

    文件上传 表单准备 要想使用 HTML 表单上传一个或多个文件 须把 HTML 表单的 enctype 属性设置为 multipart/form-data 须把 HTML 表单的method 属性设置 ...

  5. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

  6. Javacript实现字典结构

    字典是一种用[键,值]形式存储元素的数据结构.也称作映射,ECMAScript6中,原生用Map实现了字典结构. 下面代码是尝试用JS的Object对象来模拟实现一个字典结构. <script& ...

  7. HTML5轻松实现搜索框提示文字点击消失---及placeholder颜色的设置

    在做搜索框的时候无意间发现html5的input里有个placeholder属性能轻松实现提示文字点击消失功能,之前还傻傻的在用js来实现类似功能... 示例 <form action=&quo ...

  8. 【夯实PHP基础】UML序列图总结

    原文地址 序列图主要用于展示对象之间交互的顺序. 序列图将交互关系表示为一个二维图.纵向是时间轴,时间沿竖线向下延伸.横向轴代表了在协作中各独立对象的类元角色.类元角色用生命线表示.当对象存在时,角色 ...

  9. 《动手实现一个网页加载进度loading》

    loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...

  10. (资源整理)带你入门Spark

    一.Spark简介: 以下是百度百科对Spark的介绍: Spark 是一种与 Hadoop 相似的开源集群计算环境,但是两者之间还存在一些不同之处,这些有用的不同之处使 Spark 在某些工作负载方 ...