【Hibernate步步为营】--(一对多映射)之双向关联

上篇文章讨论了单向关联的一对多映射,在一的一端维护双向的关系这样的做法尽管能实现可是存在非常多缺陷,首先生成非常多多余的SQL语句,由于多的一端不维护关系,仅仅有一的一端维护,在进行操作时一的一端会发出多余的update语句;其次,由于多的一端不知道一的一端存在,所以在保存多的一端时假设外键为null值,而且在设计数据库时关系字段设为非空,则将无法保存数据。由于单向关联一对多存在非常多缺点那就没有其他的办法了吗,能够採用双向关联来优化。

一、一对多双向关联


这里继续採用上篇文章的学生和班级作为演示样例,班级和学生之间是一对多的关系,一个班级中拥有多名学生,和上篇文章不同的是这里的关系是双向的,也就是一的一端和多的一端同一时候维护关联关系,所以它的对象图例如以下:

【Hibernate步步为营】--(一对多映射)之双向关联

相应的关系模型图没有太大的变化,由于它们之间的关系是双向的,所以在关系模型中两端同一时候维护关联关系,映射到关系模型中例如以下图所看到的:

【Hibernate步步为营】--(一对多映射)之双向关联

在一对多的单向关联中映射文件仅仅须要在一的一端进行特殊配置就能够,使用<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);
}
}

相应的写入数据库中的信息列表例如以下图:

【Hibernate步步为营】--(一对多映射)之双向关联

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);
}
}

写入后相应的数据库视图例如以下:

【Hibernate步步为营】--(一对多映射)之双向关联

对照两种写入操作,由于两个写入的先后顺序不同所以出现了不同的结果,但由于是双向的关联关系所以在写入时并不会发生异常。

4、读取操作


     相对于写入数据而言,读取数据就变得非常easy了,由于是双向的关联所以数据的读取也是双向的,能够从不论什么一端读取还有一端的信息,例如以下代码:
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


结语

        双向的一对多讨论完毕,假设在使用一对多关系时建议使用双向的关联关系,它能够优化关系的类型,并且也能够保证在写入时不会出错。总结两篇文章,单向和双向性事实上是通过使用<one-to-many>和<many-to-one>来实现的,前者重在设置关联关系,并不会生成新列,可是后者在生成关联关系的同一时候会生成新列。
上一篇:IPv6 app适配


下一篇:angular中使用ngResource模块构建RESTful架构