Hibernate映射关系之一对一 and 一对多 or 多对一

时间:2021-10-01 11:58:52
个人原创,如有转载请指明出处,谢谢
将有很长一段时间不再使用Hibernate,现在将Hibernate常用的映射关系写在blog上,防止日后再次使用时又忘记了,哎,现在的脑子啊...
以学生和老师为例,首先说明单向的多对一和一对一的关系:
在同一课堂内,有N个学生,只有1个老师,所以学生对老师是单向的多对一,也可以做成双向的,但是这样就会有数据的冗余,再本章节最后才会说明双向关联
首先是老师的pojo类:
public class Teacher{
   private Integer id; //ID,标识符
   private String teacherName; //教师的名字
   ...省略getter、setter和构造方法
}
学生的pojo类:
public class Student{
   private Integer id; //ID,唯一标识符
   private String studentName; //学生的名字
   Teacher teacher; //教师类对象
    ...省略getter、setter和构造方法
}
创建教师类的映射文件 Teacher.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="Teacher" table="teacher" >

   <id name="id" type="int">
    <column name="id" />
    <generator class="identity"/>
   </id>
   <property name="teacherName" type="string"/>
</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="Student" table="student" lazy="false">
   <id name="id" type="integer">
    <column name="id" />
    <generator class="identity" />
   </id>

   <property name="studentName" type="string" />
   <many-to-one name="teacher" column="techer_id" class="Teacher" lazy="true" unique="true"/>
</class>
</hibernate-mapping>

在上述的xml中<many-to-one>标签说明存在一个多对一的映射关系,多对一的属性为Student类中的teacher属性(name的值),对应表中的列名为"teach_id"(column的值),映射关系另一方的类为Teacher类,这里需要写类全名,包括包名(class的值),lazy为延迟加载,俗称懒的初始化,为true时,在select 学生对象时,不会加载Teacher类对象,当需要一对一的关联关系时,红字部分为必须的,保证关系方的唯一性。在多对一的时候unique则为false

实际上,这里隐藏的告诉了Hibernate,teacher_id是外键,要引用Teacher类对应表的主键。

说这些是为了下面我们会使用Hibernate的一个类来为我们生成表。而不需要手动建表了:

public class CreateTable {
public static void main(String[] args) {
   Configuration config = new Configuration().configure();
   System.out.println("通过映射文件生成数据库的表");
   SchemaExport se = new SchemaExport(config);
   se.create(true, true);
   System.out.println("完成生成数据库表");
}
}

在让Hibernate为我们建表之前,需要把Teacher.hbm.xml和Student.hbm.xml写好,并且添加进Hibernate.cfg.xml中的Mapping标签中。

反过来关联,一个老师对应多个学生的单向关联,一对多的关联关系如下:

首先是老师的pojo类:
public class Teacher{
   private Integer id; //ID,标识符
   private String teacherName; //教师的名字
private Set students = new HashSet(); //学生的集合
   ...省略getter、setter和构造方法
}
学生的pojo类:
public class Student{
   private Integer id; //ID,唯一标识符
   private String studentName; //学生的名字
    ...省略getter、setter和构造方法
}
学生类的映射关系描述文件:没什么特别

<?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="Student" table="student" lazy="false">

   <id name="id" type="integer">
    <column name="id" />
    <generator class="identity" />
   </id>

   <property name="studentName" type="string" />
</class>
</hibernate-mapping>

教师类的映射关系描述文件:Teacher.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="Teacher" table="teacher">

   <id name="id" type="int">
    <column name="id" />
    <generator class="identity" />
   </id>
   <property name="teacherName" type="string" />
  
   <set name="students" inverse="false" fetch="select" cascade="save-update"
    lazy="false">
    <key column="teacher_id" />
    <one-to-many class="Student" />
   </set>

</class>
</hibernate-mapping>

关于<set>标签中的各个元素:

name:需要描述映射关系的类属性名

inverse:反转,默认为false,当为false的时候每次更新一次被关联方,就会再次更新一次关联方数据

fetch:指定了关联对象抓取的方式是select查询还是join查询,select方式时先查询返回要查询的主体对象(列表),再根据关联外键id,每一个对象发一个select查询,获取关联的对象,形成n+1次查询;而join方式,主体对象和关联对象用一句外键关联的sql同时查询出来,不会形成多次查询。如果关联的对象lazy=true,它不会去查询关联对象。

(通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中,使用HQL的左连接抓取(left join fetch) 对其进行重载。这将通知 Hibernate在第一次查询中使用外部关联(outer join),直接得到其关联数据。 在条件查询 API中,应该调用 setFetchMode(FetchMode.JOIN)语句。 )

<key column="teacher_id" />:Student类对应表的关联外键为teacher_id

<one-to-many>:关联对方的class类型为:这里要类全名,包括包名。

一对多(多对一)的双向关联:

就是把上述两种关联方式结合起来。

老师的POJO类:

public class Teacher{
   private Integer id; //ID,标识符
   private String teacherName; //教师的名字
private Set students = new HashSet(); //学生的集合
   ...省略getter、setter和构造方法
}

学生的POJO类:

public class Student{
   private Integer id; //ID,唯一标识符
   private String studentName; //学生的名字
   Teacher teacher; //教师类对象
    ...省略getter、setter和构造方法
}
教师类的映射关系描述文件:Teacher.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="Teacher" table="teacher">

   <id name="id" type="int">
    <column name="id" />
    <generator class="identity" />
   </id>
   <property name="teacherName" type="string" />
  
   <set name="students" inverse="true" fetch="select" cascade="delete"
    lazy="false">
    <key column="teacher_id" />
    <one-to-many class="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="Student" table="student" lazy="false">
   <id name="id" type="integer">
    <column name="id" />
    <generator class="identity" />
   </id>

   <property name="studentName" type="string" />
   <many-to-one name="teacher" column="techer_id" class="Teacher" lazy="true"/>
</class>
</hibernate-mapping>


注意的是,不能在双方同时指定级联操作cascade="save-update",这样会产生一个org.hibernate.ObjectDeletedException。

因为删除操作也是属于update操作,在删除时,会激发关联的save方法,这是不允许的。