Hibernate关联映射
简述:
关联关系分类:
Hibernate中的关联关系分为以上七种,有些关系可以互相转换,例如:一对多映射等同于多对一映射,只是侧重点不同。
“一对多”:一个父亲可以有多个儿子,一个儿子只能有一个父亲,侧重点在父亲。
“多对一”:一个儿子只能有一个父亲,一个父亲可以有多个儿子,侧重点在儿子。
1.单向一对多、单向多对一
单向一对多:关联关系只存在于“一”的一方,“多”的一方不存在关联关系
单向多对一:关联关系只存在于“多”的一方,“一”的一方不存在关联关系
2.双向一对多、双向多对一(重点)
“双向一对多”等同于“双向多对一”,也就是单向一对多与单向多对一的结合体。主要是在“一”的一方用一个集合来引用“多”的一方,“多”的一方用一个属性来引用“一”的一方。
此处主要用来区分inverse属性对cascade(级联)操作的影响。
以下是通过Hibernate反向工程生成的两个实体类:Student类与Classes类,Classes类作为“一”的一方,Student类作为“多”的一方。
public class Classes {
// Fields
private Integer cid;
private String cname;
private Set students = new HashSet(0);
......(省略get set方法)
} public class Student {
// Fields
private Integer sid;
private Classes classes;
private String sname;
.......(省略get set方法)
}
映射文件信息如下:
<class name="com.aaa.entity.Classes" table="classes" catalog="project">
<id name="cid" type="java.lang.Integer">
<column name="cid" />
<generator class="native" />
</id>
<property name="cname" type="java.lang.String">
<column name="cname" />
</property>
<set name="students" inverse="true">
<key>
<column name="cid" />
</key>
<one-to-many class="com.aaa.entity.Student" />
</set>
</class>
<class name="com.aaa.entity.Student" table="student" catalog="project">
<id name="sid" type="java.lang.Integer">
<column name="sid" />
<generator class="native" />
</id>
<many-to-one name="classes" class="com.aaa.entity.Classes" fetch="select">
<column name="cid" />
</many-to-one>
<property name="sname" type="java.lang.String">
<column name="sname" />
</property>
</class>
由以上映射文件可以看出:Classes类与Student类的映射文件信息中分别是通过<set>标签与<many-to-one>来维护关联关系。<set>标签中的inverse属性用来指明是否反转控制权,在不指明的情况下“一”的一方为主控方。<many-to-one>标签中的fetch用来指明加载策略。
级联添加:
inverse=true:“一”的一方最为被控方,“多”的一方作为主控方。(不指明inverse属性时,默认值为false)
添加Classes级联添加Student(不推荐使用)
将cascade="all"属性添加在Classes映射信息的set标签中,代表进行增删改操作时都级联,然后执行如下代码
public static void main(String[] args) {//例一
Session se=HibernateSessionFactory.getSession();
Transaction tx=se.beginTransaction();
Classes cla=new Classes();
cla.setCname("java2");
//向班级添加学生
Student stu=new Student();
stu.setSname("张三");
cla.getStudents().add(stu);
se.save(cla);
tx.commit();
se.close();
}
执行结果:执行了两条insert语句,成功添加了两条记录,但是student表的外键为空。
原因是因为“被控方”也就是“一”的一方没有维护关联关系。既然关联关系不存在,那么外键也肯定就不存在。
添加Student级联添加Classes
将cascade="all"属性添加在student映射信息的<many-to-one>中,然后将上面的代码改为如下所示,然后执行:
public static void main(String[] args) {//例二
Session se=HibernateSessionFactory.getSession();
Transaction tx=se.beginTransaction();
Classes cla=new Classes();
cla.setCname("java2");
Student stu=new Student();
stu.setSname("张三");
stu.setClasses(cla);
se.save(stu);
tx.commit();
se.close();
}
执行结果:执行了两条insert语句,成功添加两条记录,并且成功添加外键关系。
inverse=false:“一”的一方为主控方,“多”的一方作为被控方。
修改inverse属性为false,将控制权交给Classes,然后分别执行例一、例二
例一执行结果:执行2条insert语句后执行一条update语句用来维护关系(将student中的外键信息更新)。
例二执行结果:稍微有点出乎意料,产生了一个 org.hibernate.TransientObjectException异常:产生这个异常的原因是因为Student引用了一个瞬时状态的对象,解决这个异常只需要在保存学生之前,将班级进行保存即可。
例一的总结:进行级联添加,当被保存的对象(一)是主控方时,才会进行关系的维护,否者不维护关联关系。
例二的总结:进行级联添加,当被保存的对象(多)是主控方时,可以级联添加,否者不能引用瞬时对象,也就是不能进行 级联操作。
级联更新:(不推荐使用,所以建议将cascade属性设置为save-update)
删除一的一方,同时更新多的一方(inverse=false)
public static void main(String[] args) {
Session se=HibernateSessionFactory.getSession();
Transaction tx=se.beginTransaction();
Classes cla=(Classes) se.get(Classes.class, 6);//6代表数据库中班级的ID
se.delete(cla);
tx.commit();
se.close();
}
执行结果:Hibernate: update project.student set cid=null where cid=?
Hibernate: delete from project.classes where cid=?
从执行结果可以看出,先执行了update语句修改student表中的记录(将student中与之关联的外键设为null),然后再将此Classes删除。
然后将inverse属性设置为true,再次执行相同的操作。
执行结果:Hibernate: delete from project.student where sid=?
Hibernate: delete from project.student where sid=?
Hibernate: delete from project.classes where cid=?
从执行结果可以看出,先执行了delete语句将student中与之关联的记录删除,然后再将此Classes删除。
总结:被删除的对象(一)为主控方时,会先维护关联关系,将与之关联的外键设置为null,然后将自己删除。
被删除的对象(一)为被控方时,不维护关联关系,会先将与之关联的对象删除,然后将自己删除。
如果被删除的对象是“多”的一方,那么“一”的一方不受影响。
3.多对多
。。。。此处省略一万字。
4.一对一
实现一对一关联有两种方式,分别是主键关联和唯一外键关联。
主键关联:两个对象使用一个相同的主键值,用来表名两者之间一一对应的关系,数据库中不会有额外的字段来维护关系,仅通过表的主键来关联。
唯一外键关联:在数据库中加入唯一外键约束,使外键的值不能重复,这样就通过一对多的方式来实现了一对一关联,映射时也是通过一对多的方式来映射。
总结:主键关联映射时,因为没有直接的关联关系,所以映射到java中的实体也不存在明显的关联关系。所以推荐使用唯一外键关联的方式来实现一对一。