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

来自Wikioe
跳到导航 跳到搜索
(创建页面,内容为“category:Hibernate == 关于 == 数据库中多表之间存在着三种关联关系(一对一、一对多、多对多),用于描述实体数据之间的关系,而这种关系也可以通过对象进行描述。 实体关系 # 一对一: #: <syntaxhighlight lang="Java" highlight=""> class A{ B b; } class B{ A a; } </syntaxhighlight> #* 开发中并不常用; # '''一对多''': #: <syntaxhighlight lang="Java" highlight=""> class A{ Se…”)
 
 
(未显示同一用户的7个中间版本)
第4行: 第4行:
  数据库中多表之间存在着三种关联关系(一对一、一对多、多对多),用于描述实体数据之间的关系,而这种关系也可以通过对象进行描述。
  数据库中多表之间存在着三种关联关系(一对一、一对多、多对多),用于描述实体数据之间的关系,而这种关系也可以通过对象进行描述。


实体关系
见:
# 一对一:
# '''[[Hibernate笔记 5:实体关联关系:一对一]]'''
#: <syntaxhighlight lang="Java" highlight="">
# '''[[Hibernate笔记 5:实体关联关系:一对多]]'''
class A{
# '''[[Hibernate笔记 5:实体关联关系:多对多]]'''
B b;
}


class B{
== cascade(级联操作) ==
A a;
“级联操作”:是指当“'''主控方'''”执行“保存”、“更新”或者“删除”操作时,其关联对象(“'''被控方'''”)也执行相同的操作。
}
</syntaxhighlight>
1、在映射文件中通过对 cascade 属性的设置来控制是否对关联对象采用级联操作。
#* 开发中并不常用;
2、级联操作对各种关联关系都是有效的。
# '''一对多'''
#: <syntaxhighlight lang="Java" highlight="">
class A{
Set<B> bs;
}


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


class B{
=== '''级联的方向性''' ===
Set<A> as;
<span style="color: blue;">'''级联是有方向性的'''</span>
}
</syntaxhighlight>
    以“一对多”关系为例,“主控方为一端”(通过一端级联操作多端)和“主控方为多端”(通过多端级联操作一端),二者的含义是完全不同的。


== 一对多 ==
示例:
“一对多”关系分为“一端”与“多端”。
* “双向关联:一对多/多对一”;
* Customer.hbm.xml、LinkMan.hbm.xml 均配置了级联更新“cascade="save-update"”;
以下,讨论一对一关系。
: <syntaxhighlight lang="Java" highlight="">
@Test
// 删除联系人,并级联删除其客户
public void demo() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();


以“客户”(Customer)与“联系人”(LinkMan)为例:
// 创建一个客户
1、一个客户可以有多个联系人;
Customer customer = new Customer();
2、一个联系人只能被指定给一个客户;
customer.setCust_name("中国移动");


实体类:
// 创建三个联系人
# Customer:
LinkMan linkManl = new LinkMan();
#: <syntaxhighlight lang="Java" highlight="">
linkManl.setLkm_name("老张");
public class Customer {
private Long cust_id;  
LinkMan linkMan2 = new LinkMan();
private String cust_name;
linkMan2.setLkm_name("老李");
private String cust_industry;
private String cust_level;
LinkMan linkMan3 = new LinkMan();
private String cust_phone;  
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 语句?


// 客户类中维护联系人对象集合
private Set<LinkMan> linkMans = new HashSet<LinkMan>();


// getter、setter
tx.commit();
...
}
}
</syntaxhighlight>
</syntaxhighlight>
# LinkMan:
如上代码:
#: <syntaxhighlight lang="Java" highlight="">
# “session.save(linkManl)”:将会产生 '''4''' 条 insert 语句;
public class LinkMan {
#: 因为:linkManl 关联了 customer;而 customer 又关联了 linkMan2、linkMan3;
private Long lkm_id;
# “session.save(customer)”:将会产生 '''3''' 条 insert 语句;
private String 1km_name;
#: 因为:customer 关联了 linkMan2、linkMan3,但未关联 linkManl;
private String lkm_phone;
#: (“linkManl 关联了 customer” ≠ “customer 关联了 linkManl”)
# “session.save(linkMan2)”:将会产生 '''1''' 条 insert 语句;
#: 因为:linkMan2 未关联其他对象;
#: (“customer 关联了 linkMan2” ≠ “linkMan2 关联了 customer”)
 


// 联系人类中维护客户对象
P.S.  “a 关联了 b”  '''≠'''  “b 关联了 a”
private Customer customer;


// getter、setter
== inverse(关系维护) ==
...
inverse 属性用于设置“是否由对方维护关联关系”。
}
</syntaxhighlight>
可选值:true / false(默认)
 
inverse 作用:
# '''级联维护''':配置为 true 的一方所设置的级联将会失效。
#* inverse 的权限在 cascade 之上。
# '''外键维护''':配置为 true 的一方将不再维护关联的外键字段。
#* 维护外键的一方必须拥有对方的实例引用。
 
=== 外键维护 ===
通过如下示例,了解 inverse 对外键维护的影响。


=== 非级联操作(复杂写法) ===
“双向关联:一对多”,有配置如下:
----
创建映射:
# Customer.hbm.xml:
# Customer.hbm.xml:
#: <syntaxhighlight lang="xml" highlight="">
#: <syntaxhighlight lang="xml" highlight="">
...
...
<hibernate-mapping>
<set name="linkMans" cascade="save-update">
<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>
<key column="lkm_cust_id"></key>
<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
</set>
</set>
</class>
...
</hibernate-mapping>
</syntaxhighlight>
</syntaxhighlight>
# LinkMan.hbm.xml:
# LinkMan.hbm.xml:
#: <syntaxhighlight lang="xml" highlight="">
#: <syntaxhighlight lang="xml" highlight="">
...
...
<hibernate-mapping>
<many-to-one name="customer" cascade="save-update" class="cn.itcast.hibernate.domain.Customer" column="lkm_cust_id"/>  
<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>
</syntaxhighlight>
* 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。【下同】
* 注意:需要加载两个映射文件到核心配置文件。


测试代码:
 
'''示例1''':未配置 inverse;通过 LinkMan 级联保存。
: <syntaxhighlight lang="Java" highlight="">
: <syntaxhighlight lang="Java" highlight="">
@Test  
@Test  
// 保存:一个客户和他的两个联系人
// 保存联系人,并级联保存其客户
public void demol() {  
public void demo1() {  
Session session = HibernateUtils.openSession();  
Session session = HibernateUtils.openSession();  
Transaction tx = session.beginTransaction();  
Transaction tx = session.beginTransaction();  
第137行: 第125行:


// 创建联系人
// 创建联系人
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);
linkMan.setCustomer(customer);  
customer.getLinkMans().add(linkMan2);
linkManl.setCustomer(customer);
linkMan2.setCustomer(customer);  


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


tx.commit();
tx.commit();
}
}
</syntaxhighlight>
</syntaxhighlight>
如上代码,建立的关系是双向的:
# 建立关联时:Customer 对象关联了 LinkMan 对象,同时 LinkMan 对象也关联了 Customer 对象;
# 保存对象时:对 Customer 对象进行了保存,也分别对 LinkMan 对象进行了保存;
从控制台打印出的 SQL 语句可以看出:
# 执行了三次 insert(对 customer、linkManl、linkMan2)
# 执行了两次 update(对 linkManl、linkMan2 更新外键)
: <syntaxhighlight lang="Java" highlight="">
: <syntaxhighlight lang="Java" highlight="">
Hibernate:
Hibernate:
第170行: 第143行:
cst_customer
cst_customer
(cust_name, cust_industry, cust_level, cust_phone)
(cust_name, cust_industry, cust_level, cust_phone)
values
(?, ?, ?, ?)
Hibernate:
insert
into
cst_linkMan
(1km_name, lkm_phone, lkm_cust_id)
values
values
(?, ?, ?, ?)
(?, ?, ?, ?)
第193行: 第159行:
where
where
lkm_id=?
lkm_id=?
</syntaxhighlight>
如上,Hibernate 后台执行了一条 update 语句:在级联保存 customer 时将维护 cst_linkMan 表的 lkm_cust_id。
* 由于主控端为“多端”,所以“多端”的 insert 中外键字段不空。
*: 由于 Customer 同时维护外键,所以此处 update 语句是多余的;
'''示例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:
Hibernate:
update
insert
into
cst_linkMan
cst_linkMan
set
(1km_name, lkm_phone, lkm_cust_id)
lkm_cust_id
values
where
(?, ?, ?, ?)
lkm_id=?
</syntaxhighlight>
</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="">
@Test
// 保存客户,并级联保存其联系人
public void demo2() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();


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


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


==== 为什么需要级联? ====
// 保存
session.save(customer);


tx.commit();
}
</syntaxhighlight>
: <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 语句,但数据库 cst_linkMan 表的外键 lkm_cust_id 为 null。
* 由于主控端为“一端”,所以“多端”的 insert 中外键字段为空。
*: 由于 Customer 放弃维护外键,所以没有 update 语句;
*: 最终导致外键字段为 null。


==== 级联保存 ====


 
由以上可知,<span style="color: blue; font-size: 120%">'''对于“一对多”关系,通常由“多端”作为主控端,并维护外键'''(inverse 配置在“一端”)</span>。
==== 级联删除 ====
 
 
P.S.  “一对多”关系级联更新(保存)时:
=== 外键维护(inverse) ===
1、主控端为“'''一端'''”,sql 执行顺序: <span style="color: blue">'''insert + insert + update'''</span>
----
2、主控端为“'''多端'''”,sql 执行顺序: <span style="color: blue">'''insert + insert'''</span>
 
==== 为什么需要外键维护? ====
 
 
 
 
== 多对多 ==

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