在数据库设计方面,一对多关系关联和多对一关联关系是一样的,都是在多的一方维护关联关系,即在多的一方添加一个外键指向一的那一端(一般指向的是主键),我们不能在一的一方添加集合表示多的一方,这样也不符合数据库的设计范式。
数据库对应的实体类的关联关系的设计:
假设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