“Hibernate笔记 5:实体关联关系”的版本间差异

来自Wikioe
跳到导航 跳到搜索
 
(未显示同一用户的4个中间版本)
第4行: 第4行:
  数据库中多表之间存在着三种关联关系(一对一、一对多、多对多),用于描述实体数据之间的关系,而这种关系也可以通过对象进行描述。
  数据库中多表之间存在着三种关联关系(一对一、一对多、多对多),用于描述实体数据之间的关系,而这种关系也可以通过对象进行描述。


实体关系
见:
# 一对一:
# '''[[Hibernate笔记 5:实体关联关系:一对一]]'''
#: <syntaxhighlight lang="Java" highlight="">
# '''[[Hibernate笔记 5:实体关联关系:一对多]]'''
class A{
# '''[[Hibernate笔记 5:实体关联关系:多对多]]'''
B b;
}
 
class B{
A a;
}
</syntaxhighlight>
#* 开发中并不常用;
# '''一对多''':
#: <syntaxhighlight lang="Java" highlight="">
class A{
Set<B> bs;
}
 
class B{
A a;
}
</syntaxhighlight>
# '''多对多'''
#: <syntaxhighlight lang="Java" highlight="">
class A{
Set<B> bs;
}
 
class B{
Set<A> as;
}
</syntaxhighlight>
 
== 一对多 ==
有三种方式维护“一对多”的关联关系:
1、单向关联:一对多  ——仅由“一端”维护关联关系。
2、单向关联:多对一  ——仅由“多端”维护关联关系。
3、双向关联:一对多  ——在“一端”与“多端”均维护关联关系。
'''根据不同的情况和业务需求来选择不同的关联方式。'''
 
以“客户”(Customer)与“联系人”(LinkMan)为例:
# 一个客户可以有多个联系人;
# 一个联系人只能被指定给一个客户;
 
=== 单向关联:一对多 ===
由“'''一端'''”维护关联关系,“多端”仅维护自身属性。
 
示例:
* 在“Customer”中使用“linkMans”来维护其关联的“LinkMan”集合;
 
# Customer:
#: <syntaxhighlight lang="Java" highlight="8-9">
public class Customer {
private Long cust_id;
private String cust_name;
private String cust_industry;
private String cust_level;
private String cust_phone;
 
// 客户类中维护联系人对象集合
private Set<LinkMan> linkMans = new HashSet<LinkMan>();
 
// getter、setter
...
}
</syntaxhighlight>
#: <syntaxhighlight lang="xml" highlight="13-17">
...
<hibernate-mapping>
<class name="cn.itcast.hibernate.domain.Customer" table="cst_customer">
<id name= "cust_id">
<generator class="native"/>
</id>
<property name="cust_name" length="32"/>
<property name="cust_industry" column= "cust_industry"/>
<property name="cust_level" column= "cust_level"/>
<property name="cust_phone" column= "cust_phone"/>
<!-- 配置关联对象集合 -->
<set name="linkMans">
<key column="lkm_cust_id"></key>
<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
</set>
</class>
</hibernate-mapping>
</syntaxhighlight>
#* 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。
# LinkMan:
#: <syntaxhighlight lang="Java" highlight="">
public class LinkMan {
private Long lkm_id;
private String 1km_name;
private String lkm_phone;
 
// getter、setter
...
}
</syntaxhighlight>
#: <syntaxhighlight lang="xml" highlight="">
...
<hibernate-mapping>
<class name="cn.itcast.hibernate.domain.LinkMan" table="cst_linkMan">
<id name= "lkm_id">
<generator class="native"/>
</id>
<property name="1km_name" length="32"/>
<property name="1km_name" column= "1km_name"/>
<property name="lkm_phone" column= "lkm_phone"/>
</class>
</hibernate-mapping>
</syntaxhighlight>
 
=== 单向关联:多对一 ===
“一端”仅维护自身属性,由“'''多端'''”维护关联关系。
 
示例:
* 在“LinkMan”中使用“customer”属性来维护其关联的“Customer”;
 
# Customer:
#: <syntaxhighlight lang="Java" highlight="">
public class Customer {
private Long cust_id;
private String cust_name;
private String cust_industry;
private String cust_level;
private String cust_phone;
 
// getter、setter
...
}
</syntaxhighlight>
#: <syntaxhighlight lang="xml" highlight="">
...
<hibernate-mapping>
<class name="cn.itcast.hibernate.domain.Customer" table="cst_customer">
<id name= "cust_id">
<generator class="native"/>
</id>
<property name="cust_name" length="32"/>
<property name="cust_industry" column= "cust_industry"/>
<property name="cust_level" column= "cust_level"/>
<property name="cust_phone" column= "cust_phone"/>
</class>
</hibernate-mapping>
</syntaxhighlight>
# LinkMan:
#: <syntaxhighlight lang="Java" highlight="6-7">
public class LinkMan {
private Long lkm_id;
private String 1km_name;
private String lkm_phone;
 
// 联系人类中维护客户对象
private Customer customer;
 
// getter、setter
...
}
</syntaxhighlight>
#: <syntaxhighlight lang="xml" highlight="12-13">
...
<hibernate-mapping>
<class name="cn.itcast.hibernate.domain.LinkMan" table="cst_linkMan">
<id name= "lkm_id">
<generator class="native"/>
</id>
<property name="1km_name" length="32"/>
<property name="1km_name" column= "1km_name"/>
<property name="lkm_phone" column= "lkm_phone"/>
<!-- 配置关联对象 -->
<many-to-one name="customer" class="cn.itcast.hibernate.domain.Customer" column="lkm_cust_id"/>
</class>
</hibernate-mapping>
</syntaxhighlight>
#* 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。
 
=== 双向关联:一对多/多对一 ===
在“'''一端'''”与“'''多端'''”均维护关联关系。
 
示例:
* 在“Customer”中使用“linkMans”来维护其关联的“LinkMan”集合;
* 在“LinkMan”中使用“customer”属性来维护其关联的“Customer”;
 
# Customer:
#: <syntaxhighlight lang="Java" highlight="8-9">
public class Customer {
private Long cust_id;
private String cust_name;
private String cust_industry;
private String cust_level;
private String cust_phone;
 
// 客户类中维护联系人对象集合
private Set<LinkMan> linkMans = new HashSet<LinkMan>();
 
// getter、setter
...
}
</syntaxhighlight>
#: <syntaxhighlight lang="xml" highlight="13-17">
...
<hibernate-mapping>
<class name="cn.itcast.hibernate.domain.Customer" table="cst_customer">
<id name= "cust_id">
<generator class="native"/>
</id>
<property name="cust_name" length="32"/>
<property name="cust_industry" column= "cust_industry"/>
<property name="cust_level" column= "cust_level"/>
<property name="cust_phone" column= "cust_phone"/>
<!-- 配置关联对象集合 -->
<set name="linkMans">
<key column="lkm_cust_id"></key>
<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
</set>
</class>
</hibernate-mapping>
</syntaxhighlight>
# LinkMan:
#: <syntaxhighlight lang="Java" highlight="6-7">
public class LinkMan {
private Long lkm_id;
private String 1km_name;
private String lkm_phone;
 
// 联系人类中维护客户对象
private Customer customer;
 
// getter、setter
...
}
</syntaxhighlight>
#: <syntaxhighlight lang="xml" highlight="12-13">
...
<hibernate-mapping>
<class name="cn.itcast.hibernate.domain.LinkMan" table="cst_linkMan">
<id name= "lkm_id">
<generator class="native"/>
</id>
<property name="1km_name" length="32"/>
<property name="1km_name" column= "1km_name"/>
<property name="lkm_phone" column= "lkm_phone"/>
<!-- 配置关联对象 -->
<many-to-one name="customer" class="cn.itcast.hibernate.domain.Customer" column="lkm_cust_id"/>
</class>
</hibernate-mapping>
</syntaxhighlight>
* 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。
 
== 多对多 ==
 
 
 
 
 
 
 
 


== cascade(级联操作) ==
== cascade(级联操作) ==
  “级联操作”:是指当“'''主控方'''”执行“保存”、“更新”或者“删除”操作时,其关联对象(“被控方”)也执行相同的操作。
  “级联操作”:是指当“'''主控方'''”执行“保存”、“更新”或者“删除”操作时,其关联对象(“'''被控方'''”)也执行相同的操作。
   
   
  1、在映射文件中通过对 cascade 属性的设置来控制是否对关联对象采用级联操作。
  1、在映射文件中通过对 cascade 属性的设置来控制是否对关联对象采用级联操作。
  2、级联操作对各种关联关系都是有效的。
  2、级联操作对各种关联关系都是有效的。


=== 级联属性 ===
cascade 属性的可选值:
# <span style="color: blue">'''all'''</span>:所有情况下均进行关联操作。【即 <span style="color: blue">save-update</span> + <span style="color: blue">delete</span>】
# <span style="color: blue">'''none'''</span>:所有情况下均不进行关联操作。【默认值】
# <span style="color: blue">'''save-update'''</span>:在执行 '''save''' / '''update''' / '''saveOrUpdate''' 时进行关联操作。
# <span style="color: blue">'''delete'''</span>:在执行 '''delete''' 时进行关联操作。
# <span style="color: blue">'''all-delete-orphan'''</span>:当一个节点在对象图中成为'''孤儿节点'''时,删除该节点。


以下,以“一对多”关系为例。 【其他关联关系同理】
=== '''级联的方向性''' ===
 
  <span style="color: blue;">'''级联是有方向性的'''</span>
 
   
<span style="color: blue; font-size: 150%">'''级联是有方向性的'''</span>。    —— 通过“一端级联操作多端”与“多端级联操作一端”,二者的含义是完全不同的。
    以“一对多”关系为例,“主控方为一端”(通过一端级联操作多端)和“主控方为多端”(通过多端级联操作一端),二者的含义是完全不同的。
 
=== 级联更新 ===
----
需要在“主控端”的配置中,设置:“<span style="color: green">'''cascade="save-update"'''</span>”
 
==== 主控端:“一端” ====
主控端为“一端”,即“'''单向关联:一对多'''”。
 
Customer:
<syntaxhighlight lang="xml" highlight="3">
...
<!-- 配置关联对象集合 -->
<set name="linkMans" cascade="save-update">
<key column="lkm_cust_id"></key>
<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
</set>
...
</syntaxhighlight>
 
测试代码:
: <syntaxhighlight lang="Java" highlight="18-23">
@Test
// 保存客户,并级联保存其联系人
public void demo1() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
 
// 创建客户
Customer customer = new Customer();
customer.setCust_name("中国移动");
 
// 创建联系人
LinkMan linkManl = new LinkMan();
linkManl.setLkm_name("老张");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("老李");
// 建立关联关系
customer.getLinkMans().add(linkManl);
customer.getLinkMans().add(linkMan2);
 
// 保存
session.save(customer);
 
tx.commit();
}
</syntaxhighlight>
 
==== 主控端:“多端” ====
  主控端为“多端”,即“'''单向关联:多对一'''”。
 
LinkMan:
<syntaxhighlight lang="xml" highlight="3">
...
<!-- 配置关联对象 -->
<many-to-one name="customer" cascade="save-update" class="cn.itcast.hibernate.domain.Customer" column="lkm_cust_id"/>
...
</syntaxhighlight>
 
测试代码:
: <syntaxhighlight lang="Java" highlight="18-24">
@Test
// 保存联系人,并级联保存其客户
public void demo2() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
 
// 创建客户
Customer customer = new Customer();
customer.setCust_name("中国移动");
 
// 创建联系人
LinkMan linkManl = new LinkMan();
linkManl.setLkm_name("老张");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("老李");
// 建立关联关系
linkManl.setCustomer(customer);
linkMan2.setCustomer(customer);
 
// 保存
session.save(linkManl);
session.save(linkManl); 
 
tx.commit();
}
</syntaxhighlight>
 
=== 级联删除 ===
----
需要在“主控端”的配置中,设置:“<span style="color: green">'''cascade="delete"'''</span>”。
 
==== 主控端:“一端” ====
  主控端为“一端”,即“'''单向关联:一对多'''”。
 
Customer:
<syntaxhighlight lang="xml" highlight="3">
...
<!-- 配置关联对象集合 -->
<set name="linkMans" cascade="delete">
<key column="lkm_cust_id"></key>
<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
</set>
...
</syntaxhighlight>
 
测试代码:
: <syntaxhighlight lang="Java" highlight="18-23">
@Test
// 删除客户,并级联删除其联系人
public void demo3() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
 
Customer customer = session.get(Customer.class, 1);
session.delete(customer);
 
tx.commit();
}
</syntaxhighlight>
 
==== 主控端:“多端” ====
主控端为“多端”,即“'''单向关联:多对一'''”。
 
LinkMan:
<syntaxhighlight lang="xml" highlight="3">
...
<!-- 配置关联对象 -->
<many-to-one name="customer" cascade="delete" class="cn.itcast.hibernate.domain.Customer" column="lkm_cust_id"/>
...
</syntaxhighlight>
 
测试代码:
: <syntaxhighlight lang="Java" highlight="18-24">
@Test
// 删除联系人,并级联删除其客户
public void demo3() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
 
LinkMan linkMan = session.get(LinkMan.class, 3);
session.delete(linkMan);
 
tx.commit();
}
</syntaxhighlight>
 
=== “级联操作”与“对象关联”的理解 ===
在了解了级联更新、级联删除之后,进一步了解级联操作过程的效果。


示例:
示例:
第492行: 第81行:


== inverse(关系维护) ==
== inverse(关系维护) ==
inverse 属性用于设置“是否由对方维护关联关系”。
可选值:true / false(默认)


inverse 作用:
# '''级联维护''':配置为 true 的一方所设置的级联将会失效。
#* inverse 的权限在 cascade 之上。
# '''外键维护''':配置为 true 的一方将不再维护关联的外键字段。
#* 维护外键的一方必须拥有对方的实例引用。


== FAQ ==
=== 外键维护 ===
=== 不配置 cascade 是否可以进行“更新”? ===
  通过如下示例,了解 inverse 对外键维护的影响。
  不配置 cascade 则:不可以级联更新,但可以通过“同时保存一端和多端的对象”完成更新操作。


“双向关联:一对多”,有配置如下:
# Customer.hbm.xml:
#: <syntaxhighlight lang="xml" highlight="">
...
<set name="linkMans" cascade="save-update">
<key column="lkm_cust_id"></key>
<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
</set>
...
</syntaxhighlight>
# LinkMan.hbm.xml:
#: <syntaxhighlight lang="xml" highlight="">
...
<many-to-one name="customer" cascade="save-update" class="cn.itcast.hibernate.domain.Customer" column="lkm_cust_id"/>
...
</syntaxhighlight>


具体说明如下:


1、如果仅保存任意一方的对象,则将导致 save 操作失败。  ——由于“瞬时对象异常”(<span style="color: green">org.hibemate.TransientObjectException</span>)
'''示例1''':未配置 inverse;通过 LinkMan 级联保存。
 
示例:(“双向关联:一对多/多对一”)
: <syntaxhighlight lang="Java" highlight="">
: <syntaxhighlight lang="Java" highlight="">
@Test  
@Test  
// 保存联系人,并级联保存其客户
// 保存联系人,并级联保存其客户
public void demo3() {  
public void demo1() {  
Session session = HibernateUtils.openSession();  
Session session = HibernateUtils.openSession();  
Transaction tx = session.beginTransaction();  
Transaction tx = session.beginTransaction();  
第520行: 第129行:
// 建立关联关系
// 建立关联关系
customer.getLinkMans().add(linkMan);
linkMan.setCustomer(customer);  
linkMan.setCustomer(customer);  


// 保存
// 保存
session.save(customer);
session.save(linkMan);  
// session.save(linkMan);


tx.commit();
tx.commit();
}
}
</syntaxhighlight>
</syntaxhighlight>
如上代码,保存任意一方的对象,都会由于“持久态对象关联了瞬时态对象”,造成以下错误:
: <syntaxhighlight lang="Java" highlight="">
: <syntaxhighlight lang="Java" highlight="">
org.hibemate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: ...
Hibernate:
insert
into
cst_customer
(cust_name, cust_industry, cust_level, cust_phone)
values
(?, ?, ?, ?)
Hibernate:
insert
into
cst_linkMan
(1km_name, lkm_phone, lkm_cust_id)
values
(?, ?, ?, ?)
Hibernate:
update
cst_linkMan
set
lkm_cust_id
where
lkm_id=?
</syntaxhighlight>
</syntaxhighlight>
# “session.save(customer);”:保存 customer 时,由于不能级联保存,其中的 linkMans 集合属性的元素对象(瞬时态)未被保存,从而导致该异常;
如上,Hibernate 后台执行了一条 update 语句:在级联保存 customer 时将维护 cst_linkMan 表的 lkm_cust_id。
# “session.save(linkMan);”:保存 linkMan 时,由于不能级联保存,其中的 customer 属性的对象(瞬时态)未被保存,从而导致该异常;
* 由于主控端为“多端”,所以“多端”的 insert 中外键字段不空。
*: 由于 Customer 同时维护外键,所以此处 update 语句是多余的;


2、如果“同时保存一端和多端的对象”,则可以完成更新(代码更复杂,执行的 SQL 也更多)。  ——【'''非级联更新'''】


示例:(“双向关联:一对多/多对一”)
'''示例2''':Customer 端配置 inverse;通过 LinkMan 级联保存。
: <syntaxhighlight lang="xml" highlight="">
...
<set name="linkMans" cascade="save-update" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
</set>
...
</syntaxhighlight>
: 同样对于上述代码,后台 sql 如下:
: <syntaxhighlight lang="Java" highlight="">
Hibernate:
insert
into
cst_customer
(cust_name, cust_industry, cust_level, cust_phone)
values
(?, ?, ?, ?)
Hibernate:
insert
into
cst_linkMan
(1km_name, lkm_phone, lkm_cust_id)
values
(?, ?, ?, ?)
</syntaxhighlight>
如上,Hibernate 后台将并不执行任何 update 语句。
* 由于主控端为“多端”,所以“多端”的 insert 中外键字段不空。
*: 由于 Customer 放弃维护外键,所以没有 update 语句;
 
 
'''示例3''':Customer 端配置 inverse;通过 Customer 级联保存。
: <syntaxhighlight lang="xml" highlight="">
...
<set name="linkMans" cascade="save-update" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
</set>
...
</syntaxhighlight>
: <syntaxhighlight lang="Java" highlight="">
: <syntaxhighlight lang="Java" highlight="">
@Test  
@Test  
// 非级联保存:一个客户,及其两个联系人
// 保存客户,并级联保存其联系人
public void demo() {  
public void demo2() {  
Session session = HibernateUtils.openSession();  
Session session = HibernateUtils.openSession();  
Transaction tx = session.beginTransaction();  
Transaction tx = session.beginTransaction();  
第552行: 第217行:


// 创建联系人
// 创建联系人
LinkMan linkManl = new LinkMan();  
LinkMan linkMan = new LinkMan();  
linkManl.setLkm_name("老张");
linkMan.setLkm_name("老张");  
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("老李");
// 建立关联关系
// 建立关联关系
customer.getLinkMans().add(linkManl);
customer.getLinkMans().add(linkMan);
customer.getLinkMans().add(linkMan2);
linkManl.setCustomer(customer);
linkMan2.setCustomer(customer);  


// 保存
// 保存
session.save(customer);  
session.save(customer);  
session.save(linkManl);
session.save(linkManl);


tx.commit();
tx.commit();
}
}
</syntaxhighlight>
</syntaxhighlight>
如上代码:
: <syntaxhighlight lang="Java" highlight="">
: 对 Customer 对象进行了保存,也分别对 LinkMan 对象进行了保存;
* 从控制台打印出的 SQL 语句可以看出:
*# 执行了三次 insert(对 customer、linkManl、linkMan2)
*# 执行了两次 update(对 linkManl、linkMan2 更新外键)
*: <syntaxhighlight lang="Java" highlight="">
Hibernate:
Hibernate:
insert
insert
第593行: 第244行:
values
values
(?, ?, ?, ?)
(?, ?, ?, ?)
Hibernate:
insert
into
cst_linkMan
(1km_name, lkm_phone, lkm_cust_id)
values
(?, ?, ?, ?)
Hibernate:
update
cst_linkMan
set
lkm_cust_id
where
lkm_id=?
Hibernate:
update
cst_linkMan
set
lkm_cust_id
where
lkm_id=?
</syntaxhighlight>
</syntaxhighlight>
如上,Hibernate 后台将并不执行任何 update 语句,但数据库 cst_linkMan 表的外键 lkm_cust_id 为 null。
* 由于主控端为“一端”,所以“多端”的 insert 中外键字段为空。
*: 由于 Customer 放弃维护外键,所以没有 update 语句;
*: 最终导致外键字段为 null。


=== 不配置 cascade 是否可以进行“删除”? ===
不配置 cascade 则:可以完成删除,但未必是业务需要的效果。


示例:
由以上可知,<span style="color: blue; font-size: 120%">'''对于“一对多”关系,通常由“多端”作为主控端,并维护外键'''(inverse 配置在“一端”)</span>。
: <syntaxhighlight lang="Java" highlight="">
@Test
// 非级联删除:一个客户
P.S. “一对多”关系级联更新(保存)时:
public void demo3() {
1、主控端为“'''一端'''”,sql 执行顺序: <span style="color: blue">'''insert + insert + update'''</span>
Session session = HibernateUtils.openSession();
2、主控端为“'''多端'''”,sql 执行顺序: <span style="color: blue">'''insert + insert'''</span>
Transaction tx = session.beginTransaction();
 
Customer customer = session.get(Customer.class, 1);
session.delete(customer);
 
tx.commit();
}
</syntaxhighlight>
如上代码:
# 先将该 customer 关联的所有 LinkMan 的外键置为 null;
# 再删除该 customer。

2022年6月28日 (二) 01:00的最新版本


关于

数据库中多表之间存在着三种关联关系(一对一、一对多、多对多),用于描述实体数据之间的关系,而这种关系也可以通过对象进行描述。

见:

  1. Hibernate笔记 5:实体关联关系:一对一
  2. Hibernate笔记 5:实体关联关系:一对多
  3. Hibernate笔记 5:实体关联关系:多对多

cascade(级联操作)

“级联操作”:是指当“主控方”执行“保存”、“更新”或者“删除”操作时,其关联对象(“被控方”)也执行相同的操作。

1、在映射文件中通过对 cascade 属性的设置来控制是否对关联对象采用级联操作。
2、级联操作对各种关联关系都是有效的。

级联属性

cascade 属性的可选值:

  1. all:所有情况下均进行关联操作。【即 save-update + delete
  2. none:所有情况下均不进行关联操作。【默认值】
  3. save-update:在执行 save / update / saveOrUpdate 时进行关联操作。
  4. delete:在执行 delete 时进行关联操作。
  5. all-delete-orphan:当一个节点在对象图中成为孤儿节点时,删除该节点。

级联的方向性

级联是有方向性的:

    以“一对多”关系为例,“主控方为一端”(通过一端级联操作多端)和“主控方为多端”(通过多端级联操作一端),二者的含义是完全不同的。

示例:

  • “双向关联:一对多/多对一”;
  • Customer.hbm.xml、LinkMan.hbm.xml 均配置了级联更新“cascade="save-update"”;
	@Test 
	// 删除联系人,并级联删除其客户
	public void demo() { 
		Session session = HibernateUtils.openSession(); 
		Transaction tx = session.beginTransaction(); 

		// 创建一个客户
		Customer customer = new Customer(); 
		customer.setCust_name("中国移动"); 

		// 创建三个联系人
		LinkMan linkManl = new LinkMan(); 
		linkManl.setLkm_name("老张"); 
		
		LinkMan linkMan2 = new LinkMan(); 
		linkMan2.setLkm_name("老李");
		
		LinkMan linkMan3 = new LinkMan(); 
		linkMan3.setLkm_name("老王");
		
		
		//建立关系:
		linkManl.setCustomer(customer);
		customer.getLinkMans().add(linkMan2);
		customer.getLinkMans().add(linkMan3);
		
		// 保存
		//session.save(linkManl);    // 发送几条 insert 语句?
		//session.save(customer);    // 发送几条 insert 语句?
		//session.save(linkMan2);    // 发送几条 insert 语句?


		tx.commit();
	}

如上代码:

  1. “session.save(linkManl)”:将会产生 4 条 insert 语句;
    因为:linkManl 关联了 customer;而 customer 又关联了 linkMan2、linkMan3;
  2. “session.save(customer)”:将会产生 3 条 insert 语句;
    因为:customer 关联了 linkMan2、linkMan3,但未关联 linkManl;
    (“linkManl 关联了 customer” ≠ “customer 关联了 linkManl”)
  3. “session.save(linkMan2)”:将会产生 1 条 insert 语句;
    因为:linkMan2 未关联其他对象;
    (“customer 关联了 linkMan2” ≠ “linkMan2 关联了 customer”)


P.S.  “a 关联了 b”    “b 关联了 a”

inverse(关系维护)

inverse 属性用于设置“是否由对方维护关联关系”。

可选值:true / false(默认)

inverse 作用:

  1. 级联维护:配置为 true 的一方所设置的级联将会失效。
    • inverse 的权限在 cascade 之上。
  2. 外键维护:配置为 true 的一方将不再维护关联的外键字段。
    • 维护外键的一方必须拥有对方的实例引用。

外键维护

通过如下示例,了解 inverse 对外键维护的影响。

“双向关联:一对多”,有配置如下:

  1. Customer.hbm.xml:
    		...
    		<set name="linkMans" cascade="save-update">
    			<key column="lkm_cust_id"></key>
    			<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
    		</set>
    		...
    
  2. LinkMan.hbm.xml:
    		...
    		<many-to-one name="customer" cascade="save-update" class="cn.itcast.hibernate.domain.Customer" column="lkm_cust_id"/> 
    		...
    


示例1:未配置 inverse;通过 LinkMan 级联保存。

	@Test 
	// 保存联系人,并级联保存其客户
	public void demo1() { 
		Session session = HibernateUtils.openSession(); 
		Transaction tx = session.beginTransaction(); 

		// 创建客户
		Customer customer = new Customer(); 
		customer.setCust_name("中国移动"); 

		// 创建联系人
		LinkMan linkMan = new LinkMan(); 
		linkMan.setLkm_name("老张"); 
		
		// 建立关联关系
		linkMan.setCustomer(customer); 

		// 保存
		session.save(linkMan); 

		tx.commit();
	}
Hibernate:
	insert
	into
		cst_customer
		(cust_name, cust_industry, cust_level, cust_phone)
	values
		(?, ?, ?, ?)
Hibernate:
	insert
	into
		cst_linkMan
		(1km_name, lkm_phone, lkm_cust_id)
	values
		(?, ?, ?, ?)
Hibernate:
	update
		cst_linkMan
	set
		lkm_cust_id
	where
		lkm_id=?

如上,Hibernate 后台执行了一条 update 语句:在级联保存 customer 时将维护 cst_linkMan 表的 lkm_cust_id。

  • 由于主控端为“多端”,所以“多端”的 insert 中外键字段不空。
    由于 Customer 同时维护外键,所以此处 update 语句是多余的;


示例2:Customer 端配置 inverse;通过 LinkMan 级联保存。

		...
		<set name="linkMans" cascade="save-update" inverse="true">
			<key column="lkm_cust_id"></key>
			<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
		</set>
		...
同样对于上述代码,后台 sql 如下:
Hibernate:
	insert
	into
		cst_customer
		(cust_name, cust_industry, cust_level, cust_phone)
	values
		(?, ?, ?, ?)
Hibernate:
	insert
	into
		cst_linkMan
		(1km_name, lkm_phone, lkm_cust_id)
	values
		(?, ?, ?, ?)

如上,Hibernate 后台将并不执行任何 update 语句。

  • 由于主控端为“多端”,所以“多端”的 insert 中外键字段不空。
    由于 Customer 放弃维护外键,所以没有 update 语句;


示例3:Customer 端配置 inverse;通过 Customer 级联保存。

		...
		<set name="linkMans" cascade="save-update" inverse="true">
			<key column="lkm_cust_id"></key>
			<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
		</set>
		...
	@Test 
	// 保存客户,并级联保存其联系人
	public void demo2() { 
		Session session = HibernateUtils.openSession(); 
		Transaction tx = session.beginTransaction(); 

		// 创建客户
		Customer customer = new Customer(); 
		customer.setCust_name("中国移动"); 

		// 创建联系人
		LinkMan linkMan = new LinkMan(); 
		linkMan.setLkm_name("老张"); 
		
		// 建立关联关系
		customer.getLinkMans().add(linkMan);

		// 保存
		session.save(customer); 

		tx.commit();
	}
Hibernate:
	insert
	into
		cst_customer
		(cust_name, cust_industry, cust_level, cust_phone)
	values
		(?, ?, ?, ?)
Hibernate:
	insert
	into
		cst_linkMan
		(1km_name, lkm_phone, lkm_cust_id)
	values
		(?, ?, ?, ?)

如上,Hibernate 后台将并不执行任何 update 语句,但数据库 cst_linkMan 表的外键 lkm_cust_id 为 null。

  • 由于主控端为“一端”,所以“多端”的 insert 中外键字段为空。
    由于 Customer 放弃维护外键,所以没有 update 语句;
    最终导致外键字段为 null。


由以上可知,对于“一对多”关系,通常由“多端”作为主控端,并维护外键(inverse 配置在“一端”)。


P.S.  “一对多”关系级联更新(保存)时:
1、主控端为“一端”,sql 执行顺序: insert + insert + update
2、主控端为“多端”,sql 执行顺序: insert + insert