Hibernate一对多关联映射—单向


一、简述

一对多关联映射(one-to-many)

1、在对象模型中,一对多的关联关系,使用集合表示

比如Classes(班级)和Student(学生)之间是一对多的关系

public class Classes{

   private String id;

      private String name;

      private Set students;

}

 

public class Student{

       publicString id;

       publicString name;

}

2、我们以前学过学生对班级是多对一,返过来,班级对学生就是一对多。

3、我们多对一的关系是用户和组。返过来看,从组这边来看,就是一对多了。

下面学生的示例是班级和学生。和用户和组是一样的。

一个班级有多个学生,这是一对多的关系;返过来,多个学生属于一个班级,这就是多对一了。

4、建立对象模型

 

5、这两个对象模型之间是有关系的。我们现在讲的是一对多。一的一端是班级。多的一端是学生。那么怎么样能体现出这种关系呢?

我们在学习多对一时,是在多的一端加上一个字段。这个字段做为外键关联一的一端。多对一,就是我们在看到学生的时候,能够知道这个学生是哪个班级的。或者是当我们看到用户的时候,知道这个用户是哪个组的。所以在用户里面持有组的引用。

6、那么一对多,就是一个组里面有多少个用户。所以要维护这种关系,必须在组里面持有用户的集合。

班级和学生也是一样的。一个班级有多少学生,所以在班级里面要持有相应的学生的集合。

如下图

 

我们用Set,通常用户Set做映射。

箭头表示两者之间是有关系的。

7、上面的是对象模型,那么这种模型要映射成什么样呢?

当我们定义多对一的关系时,在加载多的一端时,能够把1的一端加载上来。因为两者之间是有关系的。

同理,一对多也是一样的,它要维护这种关系。这种关系就是一对多。一的一端要指向多。

在维护这种关系时,在加载一的时候,就会把一的一端加载上来。

也就是说,在我在加载班级时,这个班级有多少个学生,它会把所有的学生自动查询上来,放到Set集合里面。这就是维护这个关系的目的。

8、我们知道,实体类要映射成表。所在下面画两个表。

 

依我们来看,是先有班级。再分配学生。

学生有了,班级有了。要保证知道一个班级有多少学生。

因为students这个集合是在Classes上,要映射它的时候,那么我们是要在t_classes表上加一个字段,然后将所有的学生用,表达式表达出来吗?可是这样做不符合数据库的设计范式。

9、所以说,这种关系的维护应该是在t_student这一端。也就是说,在t_student表中加一个字段,这个字段就是classesid。

也就是说,一对多关联映射,要把两个表的关联字段加到多的一端。

10、所以说,一对多与多对一的映射是一样的。没有区别。都在多的一端加一个外键,指向一的一端。

但是两者也是有区别的。区别就是关系。如果维护的是多对一,则加载多的时候,能把1加上来。如果维护的是一对多,则加载班级时,能把WHSD1011对应的两个学生加载上来。

 

我的理解:对于要相关联的表来说,如果一个表想要看到对方的表内容,则就要在自己的实体类中持有对方的引用。

如果只有一方看到另一方,就是单向的。

如果要双方都看到,就要在实体模型中彼此都持有对方的引用。

 

二、新建项目hibernate_one2many_1(拷贝hibernate_session)这个项目就O了。:我们这个实例还是单向的。只能在加载班级时,把所有的学生加载上来。但是当把学生拿上来的时候,看不到这个学生所在的班级。

三、建立对象模型

1、注意:一对多关联映射,通常用Set这个集合,那么为什么用Set呢?我们可以这样理解:Set里面的对象是不能重复的。当然也可以用其他的。不过一般情况下用Set。

一定要用Set这个接口,而不用HashSet。因为Hibernate中有延迟加载。实体对象就实现了延迟加载。也就是采用代理的方式。集合也有延迟加载。hibernate中对JDK中的Set集合进行了扩展,也就是实现了这个接口,所以不能用HashSet。

所以要用Set接口。因为hibernate对Set有相应的实现,对Set进行了扩展。

2、我们的Set里面就是Student对象的集合。这样就构成了一对多的关系。

2.1 Classes.java

 

package com.bjsxt.hibernate;

import java.util.Set;

public class Classes {

    private int id;

    private String name;

    private Set students;

    public Set getStudents(){

       returnstudents;

    }

    public voidsetStudents(Set students) {

      this.students = students;

    }

   

    public int getId() {

       returnid;

    }

    public void setId(intid) {

      this.id = id;

    }

    public String getName(){

       returnname;

    }

    public voidsetName(String name) {

      this.name = name;

    }

 

}

 

2.2Student.java

 

package com.bjsxt.hibernate;

public class Student {

    private int id;

    private String name;

    public int getId() {

       returnid;

    }

    public void setId(intid) {

      this.id = id;

    }

    public String getName(){

       return name;

    }

    public voidsetName(String name) {

      this.name = name;

    }

 

}

 

四、对象模型建立好之后,就开始写映射文件,这是hibernate开发的正确思路。

1、在写映射文件时,先从简单的写起。

写Students.hmb.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>

    <classname="com.bjsxt.hibernate.Student" table="t_student">

       <idname="id">

          <!-- 主键的生成方式不能是uuid。因为这种生成方式是生成32位16进制的字符串

             而我们的实体类中id的类型为int.所以要修改主键的生成方式为native.就是以数字形式自增 -->

          <generator class="native"></generator>

       </id>

      <property name="name"/>

    </class>

</hibernate-mapping>

 

2、再映射难一点的,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-mappingpackage="com.bjsxt.hibernate">

    <classname="Classes"  table="t_classes">

       <idname="id">

        

          <generatorclass="native"></generator>

       </id>

      <property name="name"/>

       <!-- 上面为简单属性

        下面要看一下集合要如何映射

        答:集合要用set标签来映射

        set标签的名字属性的值就是Classes类中的集合类型数据的变量名 -->

       <setname="students">

         <!-- 那么要如何映射set里面的东西呢?

            我们知道ont2many的映射关系,与many2one的映射是一样的,要在多的一端加字段的。

            但是到目前为止,在t_student这张表里面是没有classid这个字段标识的。

            所以要把这个字段加过来,加过来之后,还要做为外键指向t_classes这张表的主键

            我们用key标签来实现 -->

            <!-- 在key标签里面要使用的属性是列,就是给定在t_student表中的加上的列的名字。

            加了key这个标签后,就会把classid这个字段加入到t_student这张表里面了,

            它做为外键指向t_class表的id字段。 -->

         <keycolumn="classid"></key>

         <!-- 接下来,采用one-to-many这个标签,采用这个标签,一方面是一对多,

            另一方面,要用class这个属性来指定Classes.java类中的students这个集合里面

            到底是什么元素。我们这个实例里面是Student对象集合。

            一定要指定集合中的元素是什么类型的对象。-->

         <one-to-many class="Student"/>

         <!--ont-to-many标签就代表了students集合里面是Student对象。

         这样指明之后,classes就能找到student这张表。

         因为student在这里都写了。

         而且在Student.hbm.xml文件中,也已经映射Student对象映射到哪张表了。

         这样就可以把classid加到表t_student里面,而且做为外键指向t_classes表的主键id. -->

      </set>

    </class>

</hibernate-mapping>

 

五、到hibernate.cfg.xml文件中,修改数据库,并把我们的两个实体类加到配置文件中

文件内容为:

<!DOCTYPE hibernate-configuration PUBLIC

   "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

   "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

 

<hibernate-configuration>

    <session-factory >

        <!-- 数据库改成hibernate_session-->

      <propertyname="hibernate.connection.url">jdbc:mysql://localhost/hibernate_one2many_1</property>

       <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

       <propertyname="hibernate.connection.username">root</property>

       <propertyname="hibernate.connection.password">root</property>

       <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

       <property name="hibernate.show_sql">true</property>

      

       <mapping resource="com/bjsxt/hibernate/Classes.hbm.xml"/>

       <mapping resource="com/bjsxt/hibernate/Student.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

 

六、打MySql,创建数据库

root;

create database hibernate_one2many_1;

use hibernate_one2many_1;

show tables;

 

七、执行ExportDB.class类,将对象导出为表

ExportDB.class 文件内容为:

package com.bjsxt.hibernate;

 

import org.hibernate.cfg.Configuration;

importorg.hibernate.tool.hbm2ddl.SchemaExport;

 

//这个类写完之后,以后就再也不用写了。

//这个类就是把hibernate的配置文件,转换成DDL。

public class ExportDB {

      publicstatic void main(String[] args){

         //读取hibernate.cfg.xml文件

         Configuration cfg=new Configuration().configure(); //hibernate中的一个api,是Configuraton。引入时,将光标定位在Configuration上

                        //右击,选择"Source"命令的"Add Import"命令,找到hibernate的Configuration。

                        //它是用来读取hibernate中的配置文件的。即hibernate.cfg.xml文件。(相当于Struts中的

                        //ActionServlet)

                        //这样直接写 Configuration cfg=newConfiguration();会有问题,因为

                      //hibernate有两个配置文件,一个是property类型的,一个是xml类型的,这样

                //new完,就会默认的读取property类型的配置文件,这样就会有问题。所以必须加上它的一个

                   //方法configure()。

        //hibernate中有一个工具类,叫SchemaExport,这个工具类需要传送configuration.

         //这个对象就把我们的类生成(导成)表出来。

         SchemaExport export=new SchemaExport(cfg);

         export.create(true, true);  //script是判断生成不生成,它实际是生成ddl.

                                           //这两个参数都设置成true就可以了。

                           //这个方法的具体含义可以直接看它的api。

               //或者关联上它的源代码,可以在create上,点击F3,

                 //选择"AttachResource",选择“ExternalFile",

                //再找到F:\javaprogram\Hibernate相关资料\hibernate-3.2.0.ga,就关联上了。

                

                      //export.create(..)是拿到对象,直接生成就可以了。

         

     

     

     }                

}

 

 

八、导出表后,在控制台显示:

drop table if exists t_classes

 

drop table if exists t_student

 

create table t_classes (id integer not nullauto_increment, name varchar(255), primary key (id))

create table t_student (id integer not nullauto_increment, name varchar(255), classid integer, primary key (id))

注意:在student表中加的字段classid是在写Classes.hbm.xml映射文件时,key标签起的作用哦!

 

 

alter table t_student add indexFK4B907570C229DCC9 (classid), add constraint FK4B907570C229DCC9 foreign key(classid) references t_classes (id)

这条语句是在t_student这张表中加了约束。也就是classid作为外键参照了t_classes的id字段。

 

 

2、到MysQL中

输入show tables;

desc t_classes;

+-------+--------------+------+-----+---------+----------------+

| Field |Type         | Null | Key | Default |Extra          |

+-------+--------------+------+-----+---------+----------------+

| id    |int(11)      | NO   | PRI |NULL    | auto_increment |

| name  | varchar(255) | YES |     | NULL   |               |

+-------+--------------+------+-----+---------+----------------+

 

输入命令:

desc t_student;

显示结果为:我们发现在t_student这张表中加了一个字段classid。

+---------+--------------+------+-----+---------+----------------+

| Field   |Type         | Null | Key | Default |Extra          |

+---------+--------------+------+-----+---------+----------------+

| id      |int(11)      | NO   | PRI |NULL    | auto_increment |

| name    | varchar(255) |YES  |     | NULL   |               |

| classid |int(11)      | YES  | MUL |NULL   |               |

+---------+--------------+------+-----+---------+----------------+

 

九、所以我们项目上用hibernate进行持久层映射时,最重要的是要发现对象,然后建立对象之间的关系

 

十、readme.xml文件内容为:

hibernate 一对多关联映射。(单向日葵 Classed----->Student)

   一对多关联映射利用了多对一关联映射原理。

  

   多对一关联映射,在多的一端加入一个外键,指向一的一端。但是它维护的关系是多指向一的。

   一对多关联映射,在多的一端加入一个外键,指向一的一端。但是它维护的关系是一指向多的。

   也就是说,一对多与多对一映射策略是一样的,只不过站的角度不同。

 

 

 

 

hibernate一对多关联映射——单向(继。空间不够了)

十一、接着看存储与加载

建立单元测试类One2ManyTest

package com.bjsxt.hibernate;

import java.util.HashSet;

import java.util.Set;

 

import org.hibernate.Session;

import junit.framework.TestCase;

public class One2ManyTest extends TestCase {

    }

十二、测试save(保存)

一对多的存储方式应该与多对一不一样。一对多存储的应该是classes。因为班级与学生的关系是在classes类里面的students集合。所以应该是先有学生,把学生建立好之后,再放到Classes类的students这个集合里面。

1、testSave()方法内容如下:

 

 

    public void testSave(){

       Session session=null;

       try{

          session=HibernateUtils.getSession();

          session.beginTransaction();

          

          Student student1=new Student();

          student1.setName("菜10");

          Student student2=new Student();

          student2.setName("容祖儿");

          

          Set<Student> students=new HashSet<Student>();

          students.add(student1);

          students.add(student2);

          

          Classes classes=new Classes();

          classes.setName("尚学堂");

          classes.setStudents(students);

          session.save(classes);

          

          session.getTransaction().commit();

       }catch(Exceptione){

          e.printStackTrace();

          session.getTransaction().rollback();

       }finally{

          HibernateUtils.closeSession(session);

       }

    }

   

当testSave()方法执行时,会出现这个org.hibernate.TransientObjectException: object references an unsaved transientinstance - save the transient instance before flushing:com.bjsxt.hibernate.Student 异常。

 

2、正确的保存testSave2()

 

 

    public void testSave2(){

       Session session=null;

       try{

          session=HibernateUtils.getSession();

          session.beginTransaction();

          

          //先建立学生对象集合

          Student student1=new Student();

          student1.setName("菜10");

          session.save(student1);

          Student student2=new Student();

          student2.setName("容祖儿");

          session.save(student2);

          

          //用泛型了

          //因为班级里的学生是一个集合,所以要构造一个集合出来

          Set<Student>  students=new HashSet<Student>();

          //向集合里面加数据

          students.add(student1);

          students.add(student2);

          

          //要知道哪个学生所在的班级,要new 班级出来

          Classes classes=new Classes();

          classes.setName("尚学堂");

          //这个班级里面有哪些学生,要用set方法加上去。

          //这样这个尚学堂班级里面就有两个学生了

          classes.setStudents(students);

          session.save(classes);

          

          session.getTransaction().commit();

       }catch(Exceptione){

          e.printStackTrace();

          session.getTransaction().rollback();

       }finally{

          HibernateUtils.closeSession(session);

       }

    }

2.1执行后出现SQL语句:

insert into t_student (name) values (?) 

只保存了学生的name,此时classesid字段值为null.

insert into t_student (name) values (?)

insert into t_classes (name) values (?)

hibernate又连接发了两条update,因为只有两个学生

update就是要把学生的classesid字段值更新上

根据学生id把classesid更新上。

也就是说,要由班级(一的一端)来维护这种关系:你菜10是我们班的,你容祖儿是我们班的。

在一的一端维护这种关系,会发现很多udpate。因为班级不知道会有多少学生。

就是说,由班级(一的端)主动记住这种关系

就如记人的名字一样,就如让姚明来记我们十三亿的人,这个关系要由姚来维护,

他要主动来记,把十三亿人的名字全部记下来。这肯定比较困难。因为由他一个人来更新,来记。

实际上,这种关系在多的一端来维护很容易,就如让我们记住姚明的名字很容易一样的。这是在一的端维护关系的一个缺点。另一个缺点是后面说的,如果在Student.hbm.xml映射文件中,将classesid字段设置为非空的话,则保存时会出错。

update t_student set classid=? where id=?

update t_student set classid=? where id=?

 

2.2执行结果数据库显示:

 

mysql> select * from t_classes;

+----+--------+

| id | name   |

+----+--------+

|  1 | 尚学堂 |

+----+--------+

1 row in set (0.00 sec)

 

mysql> select * from t_student;

+----+--------+--------

| id | name   |classesid

+----+--------+-------

|  1 | 菜10   |   1

+----+--------+-------

|  2 | 容祖儿  |   1

+----+--------+-------

1 row in set (0.00 sec)

 

2.3 我们现在分析一下数据库的结构,如这个测试方法,我们是先保存学生对象集合的。可是这时,这个学生对象里面只有名字与id,却没有classesid .可是表字段里是有classesid字段的。是在映射的时候加进去的。

可是如我们的测试方法,还是可以正确执行,班级里面可以加进去两个学生。这是为什么呢?

原因是,我们的classesid字段是可以为空的。

也就是说,我们先把学生保存进去,可是这时 classesid  字段是没有值的。等到保存Classes时,再更新这个字段,将班级id值更新(update)上去。

如果我们在此Students.hbm.xml文件加上这样一条,让 classesid字段不能为空,再执行这个方法时,在存储时就会失败了,因为clssesid字段在保存时必须要值,不然就存不进去了。

<set name=”students”>

   <key column=”classesid”not-null=”true”/>

   <ont-to-manyclass=”com.bjsxt.hibernate.Student”/>

</set>

注:当修改完Students.hbm.xml文件时,要重新执行ExportDB.class文件,因为这里数据库中的表结构发生改变了。所以要重新生成表。

 

十三、测试加载

 

public voidtestLoad1(){

       Session session=null;

       try{

          session=HibernateUtils.getSession();

          Classes classes=(Classes)session.load(Classes.class,1);

          Set students=classes.getStudents();

          for(Iterator iter=students.iterator();iter.hasNext();){

             Student student=(Student)iter.next();

             System.out.println("students.name="+student.getName());

          }

          session.getTransaction().commit();

          

       }catch(Exceptione){

          e.printStackTrace();

          session.getTransaction().rollback();

       }finally{

          HibernateUtils.closeSession(session);

       }

    }

   

 

   

   

    public void testLoad2(){

       Session session=null;

       try{

          session=HibernateUtils.getSession();

          session.beginTransaction();

          //注:load与get只能根据主键加载,根据别的字段是不可以的。

          //第一个参数是相应的类

          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("student.name="+student.getName());

          }

          session.getTransaction().rollback();

       }catch(Exceptione){

          e.printStackTrace();

       }finally{

          HibernateUtils.closeSession(session);

       }

    }

 

 

执行后SQL语句为:

先根据id到班级里面来查询

select classes0_.id as id0_0_, classes0_.name as name0_0_from t_classes classes0_ where classes0_.id=?

班级查上来后,就可以把班级的学生查询上来。因为班级维护了一指向多的关系,所以它要找到这个关系字段,把班级的id拿来,然后它到t_student这张表中,找维护两个表关系的字段,就是classesid=这个班级的学生,把它一个一个的拿上来,它会自动地把这些学生放到学生集合里面。

select students0_.classesid as classesid1_, students0_.idas id1_, students0_.id as id1_0_, students0_.name as name1_0_ from t_studentstudents0_ where students0_.classesid=?

 

再回想一下一对多,查询用户时,也可以把组加上来。之所以可以这样,是因为我们配置了关系,如果没有在映射文件里面配置这种关系,则hibernate就不会给我们加上来了。

 

十四、readme.txt文件

hibernate 一对多关联映射。(单向日葵Classed----->Student)

   一对多关联映射利用了多对一关联映射原理。

  

   多对一关联映射,在多的一端加入一个外键,指向一的一端。但是它维护的关系是多指向一的。

   一对多关联映射,在多的一端加入一个外键,指向一的一端。但是它维护的关系是一指向多的。

   也就是说,一对多与多对一映射策略是一样的,只不过站的角度不同。

  

因为是在一的一端维护的关系(为什么知道是在一的一端维护的关系呢?

因为我们将关系的配置写到一的一端就是Classes.hbm.xml文件里面了。

缺点:在一端维护关系的缺点:

   *如果将t_student表里的classesid字段设置为非空,则无法保存

   *因为在一的一端维护的,所以刚开始建立时,学生是不知道她是哪个班级的。

       所以一的一端要发出多余的update语句。

       因为不是在student这一端维护关系(就像我没有记住你的电话号,所以找不到你),

       所以student不知道自己是哪个班的。所以需要发出多余的update语句来更新关系。

      

基于以上的缺点,所以一对多关联映射通常要做成双向的。就可以克服这些缺点了。双向的返过来就是多对一。

为什么要做成双向的,最大的考虑,是将关系交给多的一端来维护,不让一的一端来做了。

就像不能让姚明记住十三亿人的名字一样,我们记住姚的就可以了。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值