在数据库设计方面,一对多关系关联和多对一关联关系是一样的,都是在多的一方维护关联关系,即在多的一方添加一个外键指向一的那一端(一般指向的是主键),我们不能在一的一方添加集合表示多的一方,这样也不符合数据库的设计范式。
数据库对应的实体类的关联关系的设计:
假设A关联B,A对B有依赖关系,就在A的一方添加对B的引用(即在A中添加一个关联字段关联B)。关联关系的命名一般都是把被依赖的一方放在后面表示。如A和B之间的关系是一对多的关联关系,那么就说A(一得一方)依赖了B(多的一方),要在A中添加关联字段关联B。一对多关联映射是在A中添加一个set集合关联B。
一、单向一对多关联映射示例,班级关联学生:
public class Student {
private int studentid;
private String name;
getter & setter...
}
public class Classes {
private int classid;
private String name;
private Set studets;//班级关联学生的关联关系属性
getter & setter....
}
Classes.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 package="com.wyx.hibernate">
<class name="Classes" table="t_classes">
<id name="classid">
<generator class="native"/>
</id>
<property name="name"/>
<set name="students">
<key column="classid"/><!-- 创建一列classid字段加到student表中 -->
<one-to-many class="Student"/><!-- 拿classid作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.wyx.hibernate.Student" table="t_student"> <id name="studentid"> <generator class="native"/> </id> <property name="name"/> </class> </hibernate-mapping>
测试save方法,从classes端保存:
public void testSave(){
Session session = HibernateUtils.getSession();
try {
session.beginTransaction();
Student student1 = new Student();
student1.setName("菜10");
//session.save(student1);
Student student2 = new Student();
student2.setName("虫虫");
//session.save(student2);
Set students = new HashSet<Student>();
students.add(student1);
students.add(student2);
Classes classes = new Classes();
classes.setName("BDQN_T32");
classes.setStudents(students);
session.save(classes);
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
注释部分打开才不报错,控制台输出:
Hibernate: insert into t_student (name) values (?)
Hibernate: insert into t_student (name) values (?)
Hibernate: insert into t_classes (name) values (?)
Hibernate: update t_student set classid=? where studentid=?
Hibernate: update t_student set classid=? where studentid=?
可见原理是先在student表插入数据,但关联字段classid的值为null,因为此时student是被关联的一方,student不知道有class。class是一的一方,在这里是依赖方,需要维护表之间的关联关系,classes表插入数据时,就去修改student的classid,为student添加了关联管。
测试load方法,从clssses端取出student 的信息:
public void testSLoad(){
Session session = HibernateUtils.getSession();
try {
session.beginTransaction();
Classes classes = (Classes)session.load(Classes.class, 1);
System.out.println("classes.name = "+ classes.getName());
Set<Student> students = classes.getStudents();
for(Iterator<Student> iter = students.iterator();iter.hasNext();){
Student student = iter.next();
System.out.println("classes.student.name = " + student.getName());
}
System.out.println("");
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
控制台输出:
Hibernate: select classes0_.classid as classid0_0_, classes0_.name as name0_0_ from t_classes classes0_ where classes0_.classid=?
classes.name = BDQN_T32
Hibernate: select students0_.classid as classid1_, students0_.studentid as studentid1_, students0_.studentid as studentid1_0_, students0_.name as name1_0_ from t_student students0_ where students0_.classid=?
classes.student.name = 菜10
classes.student.name = 虫虫
单向一对多映射的缺点:
1. 如果将student表的classid设置为非空限制,则无法保存。
2. 因为不是在sutdent端维护关系,所以student不知道是哪个classes的,所以需要发出多余的update语句更新关系。
所以单向一对多用的很少,最好设计成双向一对多的,把关系维护交给多的一方去处理会比较简单。
二、双向一对多关联映射示例,班级学生互相关联:
要使加载student也能够加载班级,所以在单向一对多关系映射的基础上,这样classes实体中有Set students,又在student实体中加入了一个classes字段,完成了双向关联。
classes.hbm.xml不变,下面是修改过的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.wyx.hibernate.Student" table="t_student"> <id name="studentid"> <generator class="native"/> </id> <property name="name"/> <!-- column一定要和classes.hbm.xml中的key 下的 column一致,否则classid和classes字段都会生成 --> <many-to-one name="Classes" not-null="true" column="classid"/> </class> </hibernate-mapping>
测试save方法:
public void testSave(){
Session session = HibernateUtils.getSession();
try {
session.beginTransaction();
Classes classes = new Classes();
classes.setName("BDQN_T32");
session.save(classes);//transient状态如果不保存就用会报错
Student student1 = new Student();
student1.setName("菜10");
student1.setClasses(classes);
Student student2 = new Student();
student2.setClasses(classes);
student2.setName("虫虫");
session.save(student1);
session.save(student2);
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
打印结果:
Hibernate: insert into t_classes (name) values (?)
Hibernate: insert into t_student (name, classid) values (?, ?)
Hibernate: insert into t_student (name, classid) values (?, ?)
测试load方法:
public void testLoad(){
Session session = HibernateUtils.getSession();
try {
session.beginTransaction();
Query query = session.createQuery("from Student");
for(Iterator<Student> iter = query.iterate();iter.hasNext();){
Student stu = iter.next();
System.out.println("student.name = " + stu.getName() + " student.classes = " + stu.getClasses().getName());
}
Student student = (Student)session.load(Student.class, 1);
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
打印输出:
Hibernate: select student0_.studentid as col_0_0_ from t_student student0_
Hibernate: select student0_.studentid as studentid1_0_, student0_.name as name1_0_, student0_.classid as classid1_0_ from t_student student0_ where student0_.studentid=?
Hibernate: select classes0_.classid as classid0_0_, classes0_.name as name0_0_ from t_classes classes0_ where classes0_.classid=?
student.name = 菜10 student.classes = BDQN_T32
Hibernate: select student0_.studentid as studentid1_0_, student0_.name as name1_0_, student0_.classid as classid1_0_ from t_student student0_ where student0_.studentid=?
student.name = 虫虫 student.classes = BDQN_T32
总结:通常采用一对多关联映射,都是在多的一段维护关联关系,一的一端让他失效。只需要在classes.hbm的set集合里加入inverse="true"的属性(反转)。
<set name="students" inverse="true" cascade="all">
<key column="classid"/><!-- 创建一列classid字段加到student表中 -->
<one-to-many class="Student"/><!-- 拿classid作student的外键 -->
</set>
现在再从一的一方save一个classes,调用上面单向一对多关联映射的testSave方法,注释部分就不要打开了,不然保存classes的时候还是要发update,会修改student。
这里set标签下还有了cascade ="all" ,级联操作。得到的打印结果:
Hibernate: insert into t_classes (name) values (?)
Hibernate: insert into t_student (name, classid) values (?, ?)
Hibernate: insert into t_student (name, classid) values (?, ?)
这样不管从那边存,sql打印效果都一样了,hibernate为我们做了反转。
测试load,从classes中打印student的name:
public void testInverseLoad(){
Session session = HibernateUtils.getSession();
try {
session.beginTransaction();
Classes classes = (Classes)session.load(Classes.class, 2);
for(Iterator<Student> iter = classes.getStudents().iterator();iter.hasNext();){
Student stu = iter.next();
System.out.println("classes.student.name = " + stu.getName());
}
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
Hibernate: select classes0_.classid as classid0_0_, classes0_.name as name0_0_ from t_classes classes0_ where classes0_.classid=?
Hibernate: select students0_.classid as classid1_, students0_.studentid as studentid1_, students0_.studentid as studentid1_0_, students0_.name as name1_0_, students0_.classid as classid1_0_ from t_student students0_ where students0_.classid=?
classes.student.name = liwei
classes.student.name = 张三1
测试load,从student中取classes的name:
public void testLoad1(){
Session session = HibernateUtils.getSession();
try {
session.beginTransaction();
Student stu = (Student)session.load(Student.class, 1);
System.out.println("student.class.name = " + stu.getClasses().getName());
session.getTransaction().commit();
} catch (HibernateException e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
Hibernate: select student0_.studentid as studentid1_0_, student0_.name as name1_0_, student0_.classid as classid1_0_ from t_student student0_ where student0_.studentid=?
Hibernate: select classes0_.classid as classid0_0_, classes0_.name as name0_0_ from t_classes classes0_ where classes0_.classid=?
student.class.name = BDQN_T33