Hibernate笔记 5:实体关联关系
跳到导航
跳到搜索
关于
数据库中多表之间存在着三种关联关系(一对一、一对多、多对多),用于描述实体数据之间的关系,而这种关系也可以通过对象进行描述。
实体关系
- 一对一:
class A{ B b; } class B{ A a; }
- 开发中并不常用;
- 一对多:
class A{ Set<B> bs; } class B{ A a; }
- 多对多:
class A{ Set<B> bs; } class B{ Set<A> as; }
一对多
有三种方式维护“一对多”的关联关系: 1、单向关联:一对多 ——仅由“一端”维护关联关系。 2、单向关联:多对一 ——仅由“多端”维护关联关系。 3、双向关联:一对多 ——在“一端”与“多端”均维护关联关系。 根据不同的情况和业务需求来选择不同的关联方式。
以“客户”(Customer)与“联系人”(LinkMan)为例:
- 一个客户可以有多个联系人;
- 一个联系人只能被指定给一个客户;
单向关联:一对多
由“一端”维护关联关系,“多端”仅维护自身属性。
示例:
- 在“Customer”中使用“linkMans”来维护其关联的“LinkMan”集合;
- Customer:
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:
public class LinkMan { private Long lkm_id; private String 1km_name; private String lkm_phone; // 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"/> </class> </hibernate-mapping>
单向关联:多对一
“一端”仅维护自身属性,由“多端”维护关联关系。
示例:
- 在“LinkMan”中使用“customer”属性来维护其关联的“Customer”;
- Customer:
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 ... }
... <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>
- LinkMan:
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”表)。
双向关联:一对多/多对一
在“一端”与“多端”均维护关联关系。
示例:
- 在“Customer”中使用“linkMans”来维护其关联的“LinkMan”集合;
- 在“LinkMan”中使用“customer”属性来维护其关联的“Customer”;
- Customer:
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>
- LinkMan:
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)为例:
- 一个用户可以有多个角色;
- 一个角色可以指定给多个用户;
单向关联
多对多关系的“单向关联”,即在任意一方维护关联关系即可。 而较多使用的是“双向关联”。
双向关联
示例:
- 在“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>
- 在“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(); }
如上代码:
- “session.save(linkManl)”:将会产生 4 条 insert 语句;
- 因为:linkManl 关联了 customer;而 customer 又关联了 linkMan2、linkMan3;
- “session.save(customer)”:将会产生 3 条 insert 语句;
- 因为:customer 关联了 linkMan2、linkMan3,但未关联 linkManl;
- (“linkManl 关联了 customer” ≠ “customer 关联了 linkManl”)
- “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: ...
- “session.save(customer);”:保存 customer 时,由于不能级联保存,其中的 linkMans 集合属性的元素对象(瞬时态)未被保存,从而导致该异常;
- “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 语句可以看出:
- 执行了三次 insert(对 customer、linkManl、linkMan2)
- 执行了两次 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(); }
如上代码:
- 先将该 customer 关联的所有 LinkMan 的外键置为 null;
- 再删除该 customer。