上篇文章讨论了双向关联的一对一映射,用了两个章节,主要是从主键和外键两种关联映射展开详细讨论,双向关联的映射需要在两个映射文件中分别添加相互的对应关系,斌刚在相应的类中添加对应的关联类的属性,这样在一端加载时才能加载到另一端的对象。关联中常用的主要有多对一、一对一、一对多和多对多,我们已经讨论了两种映射关系,接下来将会讨论一对多的关系。
一、单向一对多
前篇文章中曾经对多对一的关系展开了讨论,其中主要使用的是<many-to-one>的关系,在多的一端维护一的一端,那和今天要讨论的一对多的关系它们之间是否有关联呢?在关系和对象模型中存在了一对多关系所以理所当然就会有多对一的关系,Hibernate同样提供了一对多关系的标签<one-to-many>。
一对多关系的对象模型在日常生活中也经常看到,就拿学生和班级来说,一个班级里有多个学生,所以班级和学生的关系是一对多的关系,映射到对象模型中,如下图:
对象模型说明了这种一对多的关系是由一的一端来维护的,那么映射成关系模型就是一个班级字段下面会有多个学生,这样就形成了一对多的关系,通过班级能够查询获得学生信息,对应的关系模型如下图:
1、基本配置
有了对象模型接下来就让它们映射为对应的关系代码,在进行关系映射时需要在一的一端添加<one-to-many>标签,另外还需要在一的一端添加Set属性,它支持延迟加载,然后在映射文件添加set标签,并指明一对多的关系,这样就能够在一的一端查询获取多的一端。
Classes类及映射文件
它是模型中最重要的一端,在该端需要添加对应的set属性,并在配置文件中添加set标签,在set标签中配置相应的<one-to-many>对象,具体Classes.java对象代码如下:
<pre name="code" class="java">package com.src.hibernate; import java.util.Set; public class Classes { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } private String name; //Set支持延迟加载 private Set students; public Set getStudents() { return students; } public void setStudents(Set students) { this.students = students; } }
Classes对象中使用了set属性,但是只是说明了延迟加载的属性,并没有为属性配置对应的对象,属性的对象是要在映射文件中来配置的,需要添加set标签,并在set标签中添加<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.hibernate.Classes" table="t_classes"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <set name="students"> <key column="classesid"></key> <one-to-many class="com.hibernate.Student"></one-to-many> </set> </class> </hibernate-mapping>
对应的Student对象中的代码和映射文件不需要什么特殊的配置,只需要按照通常的写法编写即可,具体的配置方法不再详述,很简单。配置好后需要生成对应的SQL语句,将对象模型转化为关系模型时Hibernate生成相应的语句如下:
alter table t_student drop foreign key FK4B9075705E0AFEFE 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 FK4B9075705E0AFEFE (classesid), add constraint FK4B9075705E0AFEFE foreign key (classesid) references t_classes (id)
生成的对应的关系模型如下图:
对比SQL语句和关系模型,相应的表之间的关联是通过外键来维护的,首先是创建两张表,并指定表的主键,最后添加一对多的外键关联关系。
2、基本操作
在对数据库的操作无非是读和写两种,修改也属于写的一种,接下来看看是如何向数据库中写入和读取操作的。
写入数据
写入数据需要注意的是一对多的关系,所以在添加的时候需要添加多个学生类,另外由于在classes中添加了对应的set属性,所以在添加Student对象时应该使用HashSet来添加,这样既可实现一对多的关系,具体如下代码:
public void testSave2(){ Session session=null; try{ session=HibernateUtils.getSession(); session.beginTransaction(); Student student1=new Student(); student1.setName("zhangsan"); session.save(student1); Student student2=new Student(); student2.setName("lisi"); session.save(student2); Classes classes=new Classes(); classes.setName("ClassOne"); Set students=new HashSet(); students.add(student1); students.add(student2); classes.setStudents(students); //可以成功保存数据 //但是会发出多余的update语句来维持关系,因为是一对多的原因 session.save(classes); session.getTransaction().commit(); }catch(Exception e){ e.printStackTrace(); session.getTransaction().rollback(); }finally{ HibernateUtils.closeSession(session); } }
那么运行上面的测试用例生成的对应的数据写入到数据库中后如下图:
读取数据
写入操作相对简单,只需要把所有加载的对象都添加到Transient状态下,运行相应的方法就可以插入内容,但是对应的读取操作就会稍微复杂点,因为需要迭代获取所有的学生对象,所以这种一对多的关系效率并不很高,具体代码如下:
package com.test.hibernate; import java.util.Iterator; import java.util.Set; import com.src.hibernate.*; import junit.framework.TestCase; import org.hibernate.Session; public class One2ManyTest extends TestCase { public void testLoad1(){ Session session=null; try{ session=HibernateUtils.getSession(); session.beginTransaction(); //获取主键为5的班级信息 Classes classes=(Classes)session.load(Classes.class,5); //打印班级信息 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()); } 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=ClassOne Hibernate: select students0_.classesid as classesid1_, students0_.id as id1_, students0_.id as id0_0_, students0_.name as name0_0_ from t_student students0_ where students0_.classesid=? student.name=lisi student.name=zhangsan
结语
一对多的关系也是经常用到的,但是这种关系在加载时效率会不高,因为需要维护的数据较多,所以不建议使用一对多的关系,可以考虑多对一的关系,这样在加载时其实是一种一对一的关系,加载的效率较高,关系和对象模型得到了优化。这里只是讨论了单向的一对多,下篇文章讨论双向的关联关系。
【Hibernate步步为营】--单向关联之一对多映射