在领域模型中,类与类之间最普遍的关系就是关联关系。在 UML 中,关联是有方向的。以 Customer 和 Order 为例:一个用户能发出多个订单, 而一个订单只能属于一个客户。从 Order 到 Customer 的关联是多对一关联; 而从 Customer 到 Order 是一对多关联。
  

单向n-1的关联关系

  单向 n-1 关联只需从 n 的一端可以访问 1 的一端。
  域模型:从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性,而在 Customer 类中无需定义存放 Order 对象的集合属性。
  关系数据模型: ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键。(外键)
  Hibernate 使用 元素来映射多对一关联关系。
  例如,在Customer和Order的例子中,首先创建两个类文件:

public class Customer {private Integer customerId;private String customerName;//getters and setters
}public class Order {private Integer orderId;private String orderName;private Customer customer;//getters and setters
}

生成hibernate映射文件:

Customer.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.atguigu.hibernate.entities.n21.Customer" table="CUSTOMERS"><id name="customerId" type="java.lang.Integer"><column name="CUSTOMER_ID" /><generator class="native" /></id><property name="customerName" type="java.lang.String"><column name="CUSTOMER_NAME" /></property></class></hibernate-mapping>

Order.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 package="com.atguigu.hibernate.entities.n21"><class name="Order" table="ORDERS"><id name="orderId" type="java.lang.Integer"><column name="ORDER_ID" /><generator class="native" /></id><property name="orderName" type="java.lang.String"><column name="ORDER_NAME" /></property><!-- 映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系 name: 多这一端关联的一那一端的属性的名字class: 一那一端的属性对应的类名column: 一那一端在多的一端对应的数据表中的外键的名字--><many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one></class>
</hibernate-mapping>

先随便运行一个程序,来生成数据库表:
 
customers表

orders表


 
单向n-1的save操作:

@Testpublic void testMany2OneSave(){Customer customer = new Customer();customer.setCustomerName("AA");Order order1 = new Order();order1.setOrderName("ORDER-1");Order order2 = new Order();order2.setOrderName("ORDER-2");//设定关联关系order1.setCustomer(customer);order2.setCustomer(customer);//执行  save 操作: 先插入 Customer, 再插入 Order, 3 条 INSERT//先插入 1 的一端, 再插入 n 的一端, 只有 INSERT 语句.session.save(customer);session.save(order1);session.save(order2);//先插入 Order, 再插入 Customer. 3 条 INSERT, 2 条 UPDATE//先插入 n 的一端, 再插入 1 的一端, 会多出 UPDATE 语句!//因为在插入多的一端时, 无法确定 1 的一端的外键值. 所以只能等 1 的一端插入后, 再额外发送 UPDATE 语句.//推荐先插入 1 的一端, 后插入 n 的一端//session.save(order1);//session.save(order2);//session.save(customer);}
}

运行程序,可以成功插入记录,并且在控制台只会打印三条insert语句:

但是,如果注释掉倒数4,5,6行的代码,而使用最后的三行代码,即先保存order1和order2,再保存customer,同样也可以成功插入,但是除了会输出三行insert语句,还会输出两行update语句。如下图:

 
这是因为在先插入order记录时,无法确定外键值customer_id,只能先置为null,所以只能等customer记录插入后,再额外发送 UPDATE 语句去更新customer_id。所以,建议先插入1的那一端,即customer,后插入n的这一端,即order。

单向n-1的get操作:

@Testpublic void testMany2OneGet(){//1. 若查询多的一端的一个对象, 则默认情况下, 只查询了多的一端的对象. 而没有查询关联的 1 的那一端的对象!Order order = (Order) session.get(Order.class, 1);System.out.println(order.getOrderName()); System.out.println(order.getCustomer().getClass().getName());//session.close();//2. 只有在需要使用到关联的对象时, 才发送对应的 SQL 语句. Customer customer = order.getCustomer();System.out.println(customer.getCustomerName()); //3. 在查询 Customer 对象时, 由多的一端导航到 1 的一端时, //若此时 session 已被关闭, 则默认情况下//会发生 LazyInitializationException 异常//4. 获取 Order 对象时, 默认情况下, 其关联的 Customer 对象是一个代理对象!}

在n-1的get操作中,使用了懒加载机制。如果查询的是order对象,则默认情况下不会立即查找对应的customer对象,而只有等到需要使用这个customer对象时,才会发送select语句查询该customer对象。那么当然,如果在使用该对象之前,session被关闭了,也会抛出懒加载异常。

单向n-1的update操作:

@Testpublic void testUpdate(){Order order = (Order) session.get(Order.class, 1);order.getCustomer().setCustomerName("AAA");}

单向n-1的delete操作:

@Testpublic void testDelete(){//在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象Customer customer = (Customer) session.get(Customer.class, 1);session.delete(customer); }

在不设定级联关系的情况下,且 1 这一端的对象有 n 的对象在引用,则不能直接删除 1 这一端的对象。我们可以在customer映射文件的set节点中设置级联属性为级联删除,就可以直接删除1这一端的对象:

<set name="orders" table="ORDERS" cascade="delete">

除了级联删除之外,还有其他的级联属性,如下图所示:

但是在开发时并不建议设定级联属性,而建议使用手工的方式来处理。
 

双向1-n的关联关系

  双向 1-n 与双向 n-1 是完全相同的两种情形。
  双向 1-n 需要在1的一端可以访问n的一端,反之亦然。
  域模型:从Order到Customer的多对一双向关联需要在Order类中定义一个Customer属性,而在Customer类中需定义存放Order对象的集合属性。
  关系数据模型: ORDERS表中的CUSTOMER_ID参照CUSTOMER表的主键。(外键)
  
几个注意点:
1. 当 Session从数据库中加载Java集合时,创建的是Hibernate内置集合类的实例,因此在持久化类中定义集合属性时必须把属性声明为Java 接口类型,例如应该声明为Set而不是HashSet。Hibernate 的内置集合类具有集合代理功能,支持延迟检索策略。类似于在单向n-1关系的get操作,如果在双向1-n的get操作中获取了customer对象,如果不使用它存放order的集合,那么这个集合就不会被加载,只有使用到时才会加载。
2. 在customer类中定义集合属性时,通常把它初始化为集合实现类的一个实例,这样可以提高程序的健壮性,避免应用程序访问取值为null的集合的方法抛出NullPointerException。

Hibernate 使用 元素来映射set类型的属性。下面我们仍以customer和order的例子来测试,首先创建两个类:

public class Customer {private Integer customerId;private String customerName;/** 1. 声明集合类型时, 需使用接口类型, 因为 hibernate 在获取* 集合类型时, 返回的是 Hibernate 内置的集合类型, 而不是 JavaSE 一个标准的集合实现. * 2. 需要把集合进行初始化, 可以防止发生空指针异常*/private Set<Order> orders = new HashSet<>();//getters and setters
}public class Order {private Integer orderId;private String orderName;private Customer customer;//getters and setters
}

生成Hibernate映射文件:

Order.hbm.xml(和单向n-1相同)

<?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 package="com.atguigu.hibernate.entities.n21.both"><class name="Order" table="ORDERS"><id name="orderId" type="java.lang.Integer"><column name="ORDER_ID" /><generator class="native" /></id><property name="orderName" type="java.lang.String"><column name="ORDER_NAME" /></property><!-- 映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系 name: 多这一端关联的一那一端的属性的名字class: 一那一端的属性对应的类名column: 一那一端在多的一端对应的数据表中的外键的名字--><many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one></class>
</hibernate-mapping>

Customer.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 package="com.atguigu.hibernate.entities.n21.both"><class name="Customer" table="CUSTOMERS"><id name="customerId" type="java.lang.Integer"><column name="CUSTOMER_ID" /><generator class="native" /></id><property name="customerName" type="java.lang.String"><column name="CUSTOMER_NAME" /></property><!-- 映射 1 对多的那个集合属性 --><!-- set: 映射 set 类型的属性, name:一的这一端关联的多的那一端的属性名,table: set 中的元素对应的记录放在哪一个数据表中. 该值需要和多对一的多的那个表的名字一致 --><set name="orders" table="ORDERS"><!-- 指定关联的表中的外键列的名字 --><key column="CUSTOMER_ID"></key><!-- 指定映射类型 --><one-to-many class="Order"/></set></class></hibernate-mapping>

生成的数据库表的结构和单向n-1中相同,现在来测试各种方法:

双向1-n的save操作:

@Testpublic void testOne2ManySave(){Customer customer = new Customer();customer.setCustomerName("AA");Order order1 = new Order();order1.setOrderName("ORDER-1");Order order2 = new Order();order2.setOrderName("ORDER-2");//设定关联关系order1.setCustomer(customer);order2.setCustomer(customer);customer.getOrders().add(order1);customer.getOrders().add(order2);//执行  save 操作: 先插入 Customer, 再插入 Order, 3 条 INSERT, 2 条 UPDATE//因为 1 的一端和 n 的一端都维护关联关系. 所以会多出 UPDATE//可以在 1 的一端的 set 节点指定 inverse=true, 来使 1 的一端放弃维护关联关系!//建议设定 set 的 inverse=true, 建议先插入 1 的一端, 后插入多的一端//好处是不会多出 UPDATE 语句session.save(customer);session.save(order1);session.save(order2);//先插入 Order, 再插入 Cusomer, 3 条 INSERT, 4 条 UPDATE//session.save(order1);//session.save(order2);//session.save(customer);}
}

当我们使用倒数第4,5,6行代码进行插入,即先保存customer,再保存order1和order2,这不同于单向n-1中的操作,虽然插入都是成功的,但是此处除了会打印三条insert语句,还会打印两条update语句:

这是因为, 由于是双向的关联关系,所以 1 的一端和 n 的一端都需要维护关联关系。当先插入customer对象后,customer中的set集合里面的order的id也还未知,会先被置为null,所以当order1和order2被插入后,会多出两条update语句。那么可以推测出,如果注释掉倒数第4,5,6行代码,而执行最后三行代码,那么除了有三条insert语句,还会打印出4条update语句,因为order1和order2先各维护一次,customer会再维护两次。如下图:

  那么,如果我们不希望两端都维护关联关系,该怎么办呢?
  解决办法是,在hibernate的配置文件中可以通过设置inverse属性来决定是由双向关联的哪一方来维护表和表之间的关系。inverse = false的为主动方,inverse = true 的为被动方。由主动方负责维护关 联关系。在没有设置inverse属性的情况下,默认父子两边都维护父子关系。
  在双向 1-n 关系中,将 n 方设为主控方将有助于性能改善,而如果将 1 方设为主控方会额外多出update语句。这好比如果要国家元首记住全国人民的名字不太现实,但要让全国人民知道国家元首,就容易得多。
  例如,现在我们在Customer.hbm.xml映射文件中设置set节点为:

<set name="orders" table="ORDERS" inverse="true">

然后在test方法中先插入customer,再插入order1和order2,则只会打印三条insert语句,而不会打印update语句:

双向1-n的get操作:

@Testpublic void testOne2ManyGet(){//1. 对 n 的一端的集合使用延迟加载Customer customer = (Customer) session.get(Customer.class, 7);System.out.println(customer.getCustomerName()); //2. 返回的多的一端的集合时 Hibernate 内置的集合类型. //该类型具有延迟加载和存放代理对象的功能. System.out.println(customer.getOrders().getClass()); //session.close();//3. 可能会抛出 LazyInitializationException 异常 System.out.println(customer.getOrders().size()); //4. 再需要使用集合中元素的时候进行初始化. }

  与单向n-1的get操作类似,在双向1-n的get操作中,如果先加载了customer对象,在使用它的orders集合之前,是不会加载orders集合的,这使用了懒加载机制,那么同样,也有可能抛出懒加载异常。
  
  
双向1-n的update操作:

@Testpublic void testUpdat2(){Customer customer = (Customer) session.get(Customer.class, 1);customer.getOrders().iterator().next().setOrderName("GGG"); }

可以通过1这一端的customer来更新n那一端的order。
 
双向1-n的delete操作:

@Testpublic void testDelete(){//在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象Customer customer = (Customer) session.get(Customer.class, 1);session.delete(customer); }

同单向n-1中一样,在不设定级联关系的情况下,且 1 这一端的对象有 n 的对象在引用,则不能直接删除 1 这一端的对象。
 
 
  此外,我们还可以在customer映射文件的set节点中设置order-by属性, 在查询时对集合中的元素进行排序,order-by 中使用的是表的字段名, 而不是持久化类的属性名。例如,对orders集合根据orderName进行降序排列:

<set name="orders" table="ORDERS" order-by="ORDER_NAME DESC">

  最后补充一下,关于双向1-n的两个类的映射文件中,属性的对应关系,如下图所示:
  

Hibernate的一对多关联关系(单向和双向)相关推荐

  1. (八)Hibernate的一对多关联关系

    一.概述 例如,以客户(Customer)和订单(Order)为例,一个客户能有多个订单,一个订单只能有一个客户. 从Customer到Order是一对多关联,在java类中的面向对象设计应该一个Cu ...

  2. hibernate 的一对多关联关系映射配置

    hibernate 是操作实体类: 表是一对多的关系,当创建这2个实体的时候 在一的一方定义一个多的一方的集合 在多的一方定义一个一的一方的对象 表是多对多的关系,当创建这2个实体的时候 在互相中都有 ...

  3. hibernate映射一对多双向关联关系实例

    在电子商务应用中,经常会有这样的需求:根据给定的客户,得到该客户的所有订单:根据给定的订单,得到该订单的所属客户.对于这种双向关联的情况,在Hibernate应用中,也有人叫多对一双向关联,只是叫法不 ...

  4. Hibernate - 单向一对多关联关系映射

    上篇博文描述了Hibernate - 单向多对一关联关系映射,本篇博文继续学习单向一对多关系映射. 这里Customer:Order= 1:N,外键保存在Order表中. [1]修改Customer和 ...

  5. (转)Hibernate框架基础——一对多关联关系映射

    http://blog.csdn.net/yerenyuan_pku/article/details/52746413 上一篇文章Hibernate框架基础--映射集合属性详细讲解的是值类型的集合(即 ...

  6. hibernate的一对多双向关联映射----客户和订单关系

    客户和订单关系:客户是一的一方,订单是多的一方. customer表: CREATE TABLE `customer` ( `ID` int(4) NOT NULL AUTO_INCREMENT , ...

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

    上篇文章讨论了单向关联的一对多映射,在一的一端维护双向的关系这种做法虽然能实现但是存在很多缺陷,首先生成很多多余的SQL语句,因为多的一端不维护关系,只有一的一端维护,在进行操作时一的一端会发出多余的 ...

  8. 【SSH系列】Hibernate映射 -- 一对多关联映射

         映射原理        一对多关联映射和多对一关联映射的映射原理是一样一样的,所以说嘛,知识都是相通的,一通百通,为什么说一对多关联映射和多对一关联映射是一样的呢?因为她们都是在多的一端加入 ...

  9. WCF简单教程(6) 单向与双向通讯

    第六篇:单向与双向通讯 项目开发中我们时常会遇到需要异步调用的问题,有时忽略服务端的返回值,有时希望服务端在需要的时候回调,今天就来看看在WCF中如何实现. 先看不需要服务端返回值的单向调用,老规矩, ...

最新文章

  1. AMD and CMD are dead之KMD.js版本0.0.2发布
  2. 微信小程序篇(微信小程序的支付)
  3. 构建根文件系统之busybox(一)浅析
  4. 30岁学python编程_朋友问我,你都30岁了学编程来得及吗
  5. Java序列化简单例子
  6. Java技术:Optional 相关用法介绍笔记
  7. 对于Office Live平台的思考
  8. linux运行雷神之锤,Ubuntu18.04下可以完美运行Quake3..
  9. train problem I (栈水题)
  10. 从Java到Ruby——我的最近一次技术转型
  11. 开源代码MyCommons
  12. 常见makefile写法
  13. 电机扭矩计算公式T=9550*P/n
  14. SSIS script task发邮件
  15. java 微博阅读量怎么算,新浪微博阅读量怎么算
  16. 大量数据表的优化方案
  17. react梳理之redux
  18. matlab 贪吃的蛇,贪吃的蛇教案
  19. 海银财富领军人物:韩宏伟(军人企业家)
  20. bash入门脚本(未完善)

热门文章

  1. Unity编辑器UnityEditor基础(二)
  2. 江苏省出台居民基本养老保险新规 缴费标准提高.2015.01.01
  3. 自动驾驶 | Apollo无人驾驶课程笔记3-定位
  4. 【mmdetection】使用心得
  5. 学生怎么样充分使用服务器
  6. MyBatis的缓存问题
  7. 周大福ctf是什么意思_CTF是什么意思|周大福CTF2又是指的什么
  8. PDF怎么直接绘图?这些渠道可以试试
  9. extern、定义和声明
  10. Nordic开发问题记录