【Hibernate步步为营】--(一对多映射)之双向关联
上篇文章讨论了单向关联的一对多映射,在一的一端维护双向的关系这种做法虽然能实现但是存在很多缺陷,首先生成很多多余的SQL语句,因为多的一端不维护关系,只有一的一端维护,在进行操作时一的一端会发出多余的update语句;其次,因为多的一端不知道一的一端存在,所以在保存多的一端时如果外键为null值,并且在设计数据库时关系字段设为非空,则将无法保存数据。因为单向关联一对多存在很多缺点那就没有其它的办法了吗,可以采用双向关联来优化。
一、一对多双向关联
这里继续采用上篇文章的学生和班级作为示例,班级和学生之间是一对多的关系,一个班级中拥有多名学生,和上篇文章不同的是这里的关系是双向的,也就是一的一端和多的一端同时维护关联关系,所以它的对象图如下:
对应的关系模型图没有太大的变化,因为它们之间的关系是双向的,所以在关系模型中两端同时维护关联关系,映射到关系模型中如下图所示:
在一对多的单向关联中映射文件只需要在一的一端进行特殊配置就可以,使用<one-to-many>配置,并在对象模型中使用set迭代器来设置外联的对象模型,但是不同的是在双向的关联中需要在多的一端添加对应的另一端的外键关联,这时候就必须在多的一端使用<many-to-one>的关联关系来标明这种双向性。
1、映射
这里还使用Classes和Student来做示例,在Classes一端的内容和上文相同不会发生变换,但是多的一端Student的配置会发生变化,也就是在映射文件中需要添加<many-to-one>标签。
Student.hbm.xml映射文件配置需要添加外键列<many-to-one>标签,并且该列的名称要和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> <class name="com.src.hibernate.Student" table="t_student"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <!-- 在多的一端Student中添加一行新的Classes列 ,并且列的名称要和Classes.hbm.xml的列明相同--> <many-to-one name="classes" column="classesid"></many-to-one> </class> </hibernate-mapping>
Classes.hbm.xml映射文件的配置和上篇文章相同,需要注意的是在Classes.java文件中添加了set属性映射对应了Student对象,所以在映射文件中需要添加set标签来指示为对象模型中使用了set迭代器,具体配置如下代码:
<?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.src.hibernate.Classes" table="t_classes"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <set name="students" inverse="true"> <key column="classesid"></key> <one-to-many class="com.src.hibernate.Student"></one-to-many> </set> </class> </hibernate-mapping>
2、类
映射文件的配置是直接对应着类来的,所以有了映射文件就能够写出相应的类,相同的有了类就能够知道对应的映射文件如何编写,那来看看相应的类代码如何编写。
Student.java类,需要在类中添加关联的班级对象属性,在加载Student时能获得Classes的相关信息。
package com.src.hibernate; public class Student { //关联的班级对象 private Classes classes; public Classes getClasses() { return classes; } public void setClasses(Classes classes) { this.classes = classes; } //学生id private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } //学生姓名 private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
Classes.java文件具体代码内容见上篇文章,这里就不在详述。
有了对象模型接下来生成关系模型,生成的SQL语句如下:
alter table t_student drop foreign key FK4B907570FC588BF4 drop table if exists t_classes drop table if exists t_student create table t_classes (id integer not null auto_increment, name varchar(255), primary key (id)) create table t_student (id integer not null auto_increment, name varchar(255), classesid integer, primary key (id)) alter table t_student add index FK4B907570FC588BF4 (classesid), add constraint FK4B907570FC588BF4 foreign key (classesid) references t_classes (id)
3、数据操作
建立表结构后来编写测试方法来验证数据的操作,首先来看看数据的插入,向表结构中插入数据,写入数据时会有两种情况,一种是首先创建一个Classes对象,并将对象写入到数据库中,然后创建Student对象,在Classes对象中添加学生对象;另外一种是先创建学生对象,并将学生对象写入数据库中,然后创建Classes对象将学生对象加入到Classes对象中,这两种类型的操作最后是不相同的,来对比下。
3.1 先写班级后写学生
先把班级写入到数据库中后,Classes对象进入了Transient状态,并在数据库中有了一行,这时再写Student对象,Student对象会查找对应的Classes的主键将其写入到表中,所以此时关系模型中的数据都是非空的,保存的代码如下:
public void testSave(){ Session session=null; try{ //创建session对象 session=HibernateUtils.getSession(); //开启事务 session.beginTransaction(); //创建班级对象,将班级对象写入到数据库中 Classes classes=new Classes(); classes.setName("class"); session.save(classes); //创建学生1对象,将学生对象写入到数据库中 Student student1=new Student(); student1.setName("zhangsan"); student1.setClasses(classes); session.save(student1); //创建学生2对象,将学生对象写入到数据库中 Student student2=new Student(); student2.setName("lisi"); student2.setClasses(classes); session.save(student2); session.getTransaction().commit(); }catch(Exception e){ e.printStackTrace(); session.getTransaction().rollback(); }finally{ HibernateUtils.closeSession(session); } }
对应的写入数据库中的信息列表如下图:
3.2 先写学生后写班级
先把学生写入到数据库中此时因为学生表需要获取对应的班级列的主键信息,但是因为班级信息转化到Transient状态,所以在写入学生信息时会有null值,代码如下:
public void testSave(){ Session session=null; try{ //创建session对象 session=HibernateUtils.getSession(); //开启事务 session.beginTransaction(); //创建学生1对象,将学生对象写入到数据库中 Student student1=new Student(); student1.setName("zhangsan"); session.save(student1); //创建学生2对象,将学生对象写入到数据库中 Student student2=new Student(); student2.setName("lisi"); session.save(student2); //创建班级对象 Classes classes=new Classes(); classes.setName("Classes"); //设置学生集合 Set students=new HashSet(); students.add(student1); students.add(student2); //将学生集合写入到Classes中 classes.setStudents(students); //可以成功保存数据 //但是会发出多余的update语句来维持关系,因为是一对多的原因 session.save(classes); session.getTransaction().commit(); }catch(Exception e){ e.printStackTrace(); session.getTransaction().rollback(); }finally{ HibernateUtils.closeSession(session); } }
写入后对应的数据库视图如下:
对比两种写入操作,因为两个写入的先后顺序不同所以出现了不同的结果,但因为是双向的关联关系所以在写入时并不会发生异常。
4、读取操作
public void testLoad1(){ Session session=null; try{ session=HibernateUtils.getSession(); session.beginTransaction(); //通过班级读取学生信息 Classes classes=(Classes)session.load(Classes.class,1); System.out.println("classes.name="+classes.getName()); Set students=classes.getStudents(); for(Iterator iter=students.iterator();iter.hasNext();){ Student student=(Student)iter.next(); System.out.println("student.name="+student.getName()); } //通过学生信息读取班级信息 Student stu=new Student(); stu=(Student)session.load(Student.class, 1); System.out.println("通过学生加载班级信息Classes.id= "+stu.getClasses().getId()); session.getTransaction().commit(); }catch(Exception e){ e.printStackTrace(); session.getTransaction().rollback(); }finally{ HibernateUtils.closeSession(session); } }
运行上面的测试语句,生成的对应的语句信息如下:
Hibernate: select classes0_.id as id1_0_, classes0_.name as name1_0_ from t_classes classes0_ where classes0_.id=? classes.name=class Hibernate: select students0_.classesid as classesid1_, students0_.id as id1_, students0_.id as id0_0_, students0_.name as name0_0_, students0_.classesid as classesid0_0_ from t_student students0_ where students0_.classesid=? student.name=lisi student.name=zhangsan 通过学生加载班级信息Classes.id= 1