一对多和上文讲的多对一两种映射关系,其实就是站在相反的角度考虑同样的事情。
一对多和多对一映射原理是一样的,都在多的一端加入一个外键指向一的一端。也就是说,在关系数据库的表中,他们的表及表字段都是一样的。
他们的不同在于维护的关系:
多对一维护的关系——多指向一的关系,如果维护了多指向一的关系,那么加载多的时候会把一加载上来。
一对多维护的关系——一指向多的关系,如果维护了一指向多的关系,那么加载多的时候,会把一加载上来。
现在假如要用一对多映射描述学生和班级的关系。
1、hibernate单向一对多关联映射(Classes----->Student)
实体Classes。一对多关系,加载时需要在一的一端(classes端)加载上来多的一端(Student),所以实体Classes中需包含实体Student的引用。
package com.lzq.hibernate;
import java.util.Set;
public class Classes {
private int id;
private String name;
private Set student;
此处省略getter和setter方法
}
实体Student
package com.lzq.hibernate;
public class Student {
private int id;
private String name;
此处省略getter和setter方法
}
映射文件Classes.hbm.xml。一对多关系中,需要在多的一端维护关系,所以在Classes端加入“one-to-many”标签维护关系。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.lzq.hibernate.Classes" table="t_classes">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!-- 集合映射set-->
<set name="student">
<!-- 设置classesid外键为非空字段-->
<key column="classesid" not-null="true"/>
<one-to-many class="com.lzq.hibernate.Student"/>
</set>
</class>
</hibernate-mapping>
映射文件Student.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.lzq.hibernate.Student" table="t_student">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
</class>
</hibernate-mapping>
尽管我们完成映射,但是这样映射还存在一定的问题,看下面的加载:
public void testOne2ManyAdd() throws Exception {
Session session=null;
Transaction tx=null;
try {
session=HibernateUtils.getSession();
tx=session.beginTransaction();
Student student1=new Student();
student1.setName("张三");
session.save(student1);
Classes classes=new Classes();
classes.setName("提高班");
Set students=new HashSet();
students.add(student1);
classes.setStudent(students);
session.save(classes);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
HibernateUtils.closeSession(session);
}
}
结果会抛PropertyValueException异常。原因很简单:我们在多的一端(Student)没有维护关系,也就是说,Student不知道自己是哪一个班的,所以我们在保存Student的时候关系字段classesid为null。这就限制了外键必须为允许非空,如果外键设置为“不能为空”,则无法进行保存。而我们这里对外键设置,所以才会抛出此异常。
那怎么解决该问题呢?当然,我们可以在Classes.hbm.xml配置中去掉“ not-null='true' ”,这样也可以保存,但是我们可以观察生成的SQL语句,此时进行了一条insert和update语句,显然,它实现插入了student信息,然后在更新的classesid字段。这种方式显然不友善。那么除了这种方式,还能不能用别的方式解决?
这应为此问题,才产生了双向的一对多的关联映射:
2、hibernate双向一对多关联映射(Classes<----->Student)
既然是双向一对多关联映射,那么一定要在多的一端(Student)加入一的一端(Classes)的引用,这样才能在多的一端(Student)进行存储和加载。
package com.lzq.hibernate;
public class Student {
private int id;
private String name;
private Classes classes;
此处省略getter/setter方法
}
对应配置更改:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.lzq.hibernate.Student" table="t_student">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!-- 此处需要与classes端的配置的字段保持一致-->
<many-to-one name="classes" column="classesid" />
</class>
</hibernate-mapping>
此外,既然双向一对多关联映射的产生是为了弥补单向一对多关联映射的缺陷,那么避免程序员编写程序仍然在一的一端(Classes)加载,我们可以采用反转技术,设置从Classes端加载失效:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.lzq.hibernate.Classes" table="t_classes">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!--反转:设置从Classes端加入数据失效-->
<set name="student" inverse="true">
<key column="classesid" />
<one-to-many class="com.lzq.hibernate.Student"/>
</set>
</class>
</hibernate-mapping>
为什么多对一关联映射不存在单向一对多中的问题呢?如果你明白了上面所讲的,这个问题将很好回答。在多对一关联映射里面,由于关系是在多的一端进行维护的,加载的时候从多的一端进行加载,当然没有问题。
总结一下:在多对一关联映射中,如果用到,经常采用双向的方式来完成映射,弥补单向加载时的问题。