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

来自Wikioe
跳到导航 跳到搜索
第40行: 第40行:
  有三种方式维护“一对多”的关联关系:
  有三种方式维护“一对多”的关联关系:
   
   
  1、单向关联:一对多  ——仅由“一端”维护关联关系。
  1、单向关联:一对多  ——由“'''一端'''”维护关联关系,“多端”仅维护自身属性。
  2、单向关联:多对一  ——仅由“多端”维护关联关系。
  2、单向关联:多对一  ——由“'''多端'''”维护关联关系,“一端”仅维护自身属性。
  3、双向关联:一对多  ——在“一端”与“多端”均维护关联关系。
  3、双向关联:一对多  ——在“'''一端'''”与“'''多端'''”均维护关联关系。
   
   
   
   
第52行: 第52行:


=== 单向关联:一对多 ===
=== 单向关联:一对多 ===
由“'''一端'''”维护关联关系,“多端”仅维护自身属性。
示例:
示例:
* 在“Customer”中使用“linkMans”来维护其关联的“LinkMan”集合;
: 在“Customer”中使用“linkMans”来维护其关联的“LinkMan”集合;
 
: <syntaxhighlight lang="Java" highlight="8-9">
# Customer:
#: <syntaxhighlight lang="Java" highlight="8-9">
public class Customer {  
public class Customer {  
private Long cust_id;  
private Long cust_id;  
第73行: 第69行:
}
}
</syntaxhighlight>
</syntaxhighlight>
#: <syntaxhighlight lang="xml" highlight="13-17">
: <syntaxhighlight lang="xml" highlight="13-17">
...
...
<hibernate-mapping>
<hibernate-mapping>
第94行: 第90行:
</hibernate-mapping>
</hibernate-mapping>
</syntaxhighlight>
</syntaxhighlight>
#* 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。
* 注意:“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”;
: 在“LinkMan”中使用“customer”属性来维护其关联的“Customer”;
 
: <syntaxhighlight lang="Java" highlight="6-7">
# 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 {  
public class LinkMan {  
private Long lkm_id;
private Long lkm_id;
第169行: 第108行:
}
}
</syntaxhighlight>
</syntaxhighlight>
#: <syntaxhighlight lang="xml" highlight="12-13">
: <syntaxhighlight lang="xml" highlight="12-13">
...
...
<hibernate-mapping>
<hibernate-mapping>
第186行: 第125行:
</hibernate-mapping>
</hibernate-mapping>
</syntaxhighlight>
</syntaxhighlight>
#* 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。
* 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。


=== 双向关联:一对多/多对一 ===
=== 双向关联:一对多/多对一 ===
在“'''一端'''”与“'''多端'''”均维护关联关系。
示例:
示例:
* 在“Customer”中使用“linkMans”来维护其关联的“LinkMan”集合;
# 在“Customer”中:维护其关联的“LinkMan”集合;
* 在“LinkMan”中使用“customer”属性来维护其关联的“Customer”;
 
# Customer:
#: <syntaxhighlight lang="Java" highlight="8-9">
#: <syntaxhighlight lang="Java" highlight="8-9">
public class Customer {  
public class Customer{  
private Long cust_id;  
private Long cust_id;  
private String cust_name;
private String cust_name;
第232行: 第166行:
</hibernate-mapping>
</hibernate-mapping>
</syntaxhighlight>
</syntaxhighlight>
# LinkMan:
# 在“LinkMan”中:维护其关联的“Customer”;
#: <syntaxhighlight lang="Java" highlight="6-7">
#: <syntaxhighlight lang="Java" highlight="6-7">
public class LinkMan {  
public class LinkMan{  
private Long lkm_id;
private Long lkm_id;
private String 1km_name;
private String 1km_name;

2022年6月27日 (一) 09:35的版本


关于

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

实体关系

  1. 一对一:
    class A{
    	B b
    }
    
    class B{
    	A a
    }
    
    • 开发中并不常用;
  2. 一对多
    class A{
    	Set<B> bs;
    }
    
    class B{
    	A a
    }
    
  3. 多对多
    class A{
    	Set<B> bs;
    }
    
    class B{
    	Set<A> as;
    }
    

一对多

有三种方式维护“一对多”的关联关系:

1、单向关联:一对多   ——由“一端”维护关联关系,“多端”仅维护自身属性。
2、单向关联:多对一   ——由“多端”维护关联关系,“一端”仅维护自身属性。
3、双向关联:一对多   ——在“一端”与“多端”均维护关联关系。


根据不同的情况和业务需求来选择不同的关联方式。

以“客户”(Customer)与“联系人”(LinkMan)为例:

  1. 一个客户可以有多个联系人;
  2. 一个联系人只能被指定给一个客户;

单向关联:一对多

示例:

在“Customer”中使用“linkMans”来维护其关联的“LinkMan”集合;
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
	...
}
...
<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>
  • 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。

单向关联:多对一

示例:

在“LinkMan”中使用“customer”属性来维护其关联的“Customer”;
public class LinkMan { 
	private Long lkm_id;
	private String 1km_name;
	private String lkm_phone;

	// 联系人类中维护客户对象
	private Customer customer; 

	// getter、setter
	...
}
...
<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>
  • 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。

双向关联:一对多/多对一

示例:

  1. 在“Customer”中:维护其关联的“LinkMan”集合;
    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
    	...
    }
    
    ...
    <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>
    
  2. 在“LinkMan”中:维护其关联的“Customer”;
    public class LinkMan{ 
    	private Long lkm_id;
    	private String 1km_name;
    	private String lkm_phone;
    
    	// 联系人类中维护客户对象
    	private Customer customer; 
    
    	// getter、setter
    	...
    }
    
    ...
    <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>
    
  • 注意:“lkm_cust_id”为“cst_linkMan”表的外键(参照于“cst_customer”表)。

多对多

有两种方式维护“多对多”的关联关系:

1、单向关联
2、双向关联    【常用】

以“用户”(User)与“角色”(Role)为例:

  1. 一个用户可以有多个角色;
  2. 一个角色可以指定给多个用户;

单向关联

多对多关系的“单向关联”,即在任意一方维护关联关系即可。

而较多使用的是“双向关联”。

双向关联

示例:

  1. 在“User”中:维护其关联的“Role”集合;
    public class User{ 
    	private Long user_id;
    	private Long user_code;
    	private String user_name;
    	private String user_password;
    	private String user_state;
    
    	// 用户类中维护角色对象集合
    	private Set<Role> roles = new HashSet<Role>(); 
    
    	// getter、setter
    	...
    }
    
    ...
    <hibernate-mapping>
    	<class name="cn.itcast.hibernate.domain.User" table="sys_user">
    		<id name= "user_id"> 
    			<generator class="native"/> 
    		</id>
    	
    		<property name="user_code"/> 
    		<property name="user_name"/> 
    		<property name="user_password"/> 
    		<property name="user_state"/>
    		
    		<!-- 配置关联角色集合 -->
    		<set name="roles" table="sys_user_role">
    			<key column="user_id"></key>
    			<many-to-many class= "cn.itcast.hibernate.domain.Role" column="role_id"/>
    		</set>
    	</class>
    </hibernate-mapping>
    
  2. 在“Role”中:维护其关联的“User”;
    public class Role{ 
    	private Long role_id;
    	private String role_name;
    	private String role_mome;
    
    	// 角色类中维护用户对象集合
    	private Set<User> users = new HashSet<User>(); 
    
    	// getter、setter
    	...
    }
    
    ...
    <hibernate-mapping>
    	<class name="cn.itcast.hibernate.domain.Role" table="sys_role">
    		<id name= "role_id"> 
    			<generator class="native"/> 
    		</id>
    		
    		<property name="role_name"/> 
    		<property name="role_mome"/> 
    		
    		<!-- 配置关联用户集合 -->
    		<set name="roles" table="sys_user_role">
    			<key column="role_id"></key>
    			<many-to-many class= "cn.itcast.hibernate.domain.User" column="user_id"/>
    		</set>
    	</class>
    </hibernate-mapping>
    

cascade(级联操作)

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

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


以下,以“一对多”关系为例。 【其他关联关系同理】


级联是有方向性的。     —— 通过“一端级联操作多端”与“多端级联操作一端”,二者的含义是完全不同的。

级联更新


需要在“主控端”的配置中,设置:“cascade="save-update"

主控端:“一端”

主控端为“一端”,即“单向关联:一对多”。

Customer:

		...
		<!-- 配置关联对象集合 -->
		<set name="linkMans" cascade="save-update">
			<key column="lkm_cust_id"></key>
			<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
		</set>
		...

测试代码:

	@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();
	}

主控端:“多端”

主控端为“多端”,即“单向关联:多对一”。

LinkMan:

		...
		<!-- 配置关联对象 -->
		<many-to-one name="customer" cascade="save-update" class="cn.itcast.hibernate.domain.Customer" column="lkm_cust_id"/> 
		...

测试代码:

	@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();
	}

级联删除


需要在“主控端”的配置中,设置:“cascade="delete"”。

主控端:“一端”

主控端为“一端”,即“单向关联:一对多”。

Customer:

		...
		<!-- 配置关联对象集合 -->
		<set name="linkMans" cascade="delete">
			<key column="lkm_cust_id"></key>
			<one-to-many class= "cn.itcast.hibernate.domain.LinkMan"/>
		</set>
		...

测试代码:

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

		Customer customer = session.get(Customer.class, 1);
		session.delete(customer);

		tx.commit();
	}

主控端:“多端”

主控端为“多端”,即“单向关联:多对一”。

LinkMan:

		...
		<!-- 配置关联对象 -->
		<many-to-one name="customer" cascade="delete" class="cn.itcast.hibernate.domain.Customer" column="lkm_cust_id"/> 
		...

测试代码:

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

		LinkMan linkMan = session.get(LinkMan.class, 3);
		session.delete(linkMan);

		tx.commit();
	}

“级联操作”与“对象关联”的理解

在了解了级联更新、级联删除之后,进一步了解级联操作过程的效果。

示例:

  • “双向关联:一对多/多对一”;
  • 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(关系维护)

FAQ

不配置 cascade 是否可以进行“更新”?

不配置 cascade 则:不可以级联更新,但可以通过“同时保存一端和多端的对象”完成更新操作。


具体说明如下:

1、如果仅保存任意一方的对象,则将导致 save 操作失败。  ——由于“瞬时对象异常”(org.hibemate.TransientObjectException

示例:(“双向关联:一对多/多对一”)

	@Test 
	// 保存联系人,并级联保存其客户
	public void demo3() { 
		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);
		linkMan.setCustomer(customer); 

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

		tx.commit();
	}

如上代码,保存任意一方的对象,都会由于“持久态对象关联了瞬时态对象”,造成以下错误:

org.hibemate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: ...
  1. “session.save(customer);”:保存 customer 时,由于不能级联保存,其中的 linkMans 集合属性的元素对象(瞬时态)未被保存,从而导致该异常;
  2. “session.save(linkMan);”:保存 linkMan 时,由于不能级联保存,其中的 customer 属性的对象(瞬时态)未被保存,从而导致该异常;
2、如果“同时保存一端和多端的对象”,则可以完成更新(代码更复杂,执行的 SQL 也更多)。  ——【非级联更新

示例:(“双向关联:一对多/多对一”)

	@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("老李");
		
		// 建立关联关系
		customer.getLinkMans().add(linkManl);
		customer.getLinkMans().add(linkMan2);
		
		linkManl.setCustomer(customer); 
		linkMan2.setCustomer(customer); 

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

		tx.commit();
	}

如上代码:

对 Customer 对象进行了保存,也分别对 LinkMan 对象进行了保存;
  • 从控制台打印出的 SQL 语句可以看出:
    1. 执行了三次 insert(对 customer、linkManl、linkMan2)
    2. 执行了两次 update(对 linkManl、linkMan2 更新外键)
    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:
    	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=?
    

不配置 cascade 是否可以进行“删除”?

不配置 cascade 则:可以完成删除,但未必是业务需要的效果。

示例:

	@Test 
	// 非级联删除:一个客户
	public void demo3() { 
		Session session = HibernateUtils.openSession(); 
		Transaction tx = session.beginTransaction(); 

		Customer customer = session.get(Customer.class, 1);
		session.delete(customer);

		tx.commit();
	}

如上代码:

  1. 先将该 customer 关联的所有 LinkMan 的外键置为 null;
  2. 再删除该 customer。