02多表關聯關係映射
目錄
一、搭建環境
具體參考上篇 (寫在前面,hibernate工具類有更新 改成getCurrentSession,核心配置文件裏得加一行配置,綁定開啓綁定本地session,使用時不需要手動關閉,線程結束自動關閉 不過學校上課說是上ssh,最重要的spring卻沒教。。因此不存在service層和dao層session的傳遞,用上篇的getSession也完全可以,想練練ssh整合的就改過來,反正都是死代碼,直接當模板用,直接複製粘貼就行了)
本篇的數據庫與javabean和上篇不同
兩張表:
customer.sql 客戶表create table customer ( custId bigint(32) not null auto_increment comment '客戶編號(主鍵)', custName varchar(32) not null comment '客戶名稱(公司名稱)', -- cust_linkman varchar(64) default null comment '聯繫人(一個客戶多個聯繫人,此處對應map 實際多個選項 不知道填啥)', custPhone varchar(64) default null comment '固定電話', primary key (custId) ) engine=innodb auto_increment=1 default charset=utf8;
linkman.sql 聯繫人表
create table linkman ( lkmId bigint(32) not null auto_increment comment '聯繫人編號(主鍵)', lkmName varchar(16) default null comment '聯繫人姓名', custId bigint(32) not null comment '所代表的客戶id', lkmPhone varchar(16) default null comment '聯繫人辦公電話', primary key (lkmId), -- key fk_linkman_customer_id (custid), constraint fk_linkman_customer_id foreign key (custId) references customer (custId) on delete no action on update no action ) engine=innodb auto_increment=3 default charset=utf8;
關係描述:客戶與聯繫人關係:
一對多(客戶一方 聯繫人多方)
一個客戶對應多個聯繫人,一個聯繫人對應一個客戶。
也即你是客戶,可以通過你的兄弟,朋友,父母的多個聯繫人聯繫到你。但通過你的父母只能聯繫到你這個客戶表:
多方表裏寫一方的主鍵作爲外鍵,匹配到多方唯一的一方。即:
聯繫人表裏寫客戶表的主鍵作爲外鍵,匹配到聯繫人對應的唯一的客戶javabean:
多方javabean(表對應的類對象),寫一個一方的類對象對應外鍵
一方類對象寫一個set集合,存儲對應的所有的多方對象
即:
Linkman類有一個字段屬性爲"Customer customer;",對應表的外鍵(而不是和其他屬性一下寫個簡單的字段)
Customer類有一個屬性爲"Set<Linkman> linkmans",存儲客戶所有的中介聯繫人,表中可以沒有對應的列詳細下面有
配好後目錄如下:
二、javabean和xml的基礎配置
Customer.java
package cn.ahpu.domain;
import java.util.HashSet;
import java.util.Set;
public class Customer {
private Long custId;//客戶id
private String custName;//客戶姓名
private String custPhone;//客戶電話
//一對多,一方寫集合 具體含義,一個客戶有多個聯繫人 eg:你是客戶,公司可以通過你的老師,同學,父母等好多人聯繫到你
//hibernate默認使用set集合 和配置文件對應 數據庫表裏可以沒有此屬性對應的列
private Set<Linkman> linkmans=new HashSet<Linkman>();//★ 千萬注意必須手動初始化 hibernate不會幫你new
//省略get/set 自己生成
}
Linkman.java
package cn.ahpu.domain;
public class Linkman {
private Long lkmId;
private String lkmName;
private String lkmPhone;
//一對多 多方寫類對象 具體含義 每個聯繫人只能對應一個客戶 eg:通過你的父母只能找到你,不能聯繫到別人家的孩子
private Customer customer;//外鍵-----一方類寫類對象 簡單屬性,數據庫也有對應的匹配列,因而不用自己new
//省略get/set 自己生成
}
Customer.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 約束 使有提示 -->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.ahpu.domain.Customer" table="customer">
<!-- java主鍵屬性與表的主鍵列名對應 native表示主鍵自動遞增
重要總結: 主鍵爲short,int,long用 native
主鍵char,varchar類型 即隨機字符串用:uuid
-->
<id name="custId" column="custId">
<generator class="native"/>
</id>
<!-- 其他非主鍵屬性對應的配置 name爲類屬性名 column爲表列名-->
<property name="custName" column="custName"/>
<property name="custPhone" column="custPhone"/>
<!-- javabean裏多寫了個set集合 也要專門配置
標籤名就叫set 好記
-->
<set name="linkmans">
<key column="custId"/>
<one-to-many class="cn.ahpu.domain.Linkman"/>
</set>
<!--
屬性name: 集合名稱(還是那個特殊屬性的字段名)
column: 還是多表的外鍵名稱
class: 一方當然也需要多方類的全路徑 才能幫你封裝所有的聯繫人類及其數據到我的集合裏呀
小結:一方多方對於集合或是類對象這個特殊屬性的配置本質上都只提供了3個信息,name,column,class
就是一方標籤配法麻煩點,3個標籤(每個分別一個屬性)有嵌套關係,多方一個標籤3個屬性即可
-->
</class>
</hibernate-mapping>
linkman.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 約束 使有提示 -->
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.ahpu.domain.Linkman" table="linkman">
<!-- java主鍵屬性與表的主鍵列名對應 native表示主鍵自動遞增
重要總結: 主鍵爲short,int,long用 native
主鍵char,varchar類型 即隨機字符串用:uuid
-->
<id name="lkmId" column="lkmId">
<generator class="native"/>
</id>
<!-- 其他非主鍵屬性對應的配置 name爲類屬性名 column爲表列名-->
<property name="lkmName" column="lkmName"/>
<property name="lkmPhone" column="lkmPhone"/>
<!-- 外鍵的配置
多方就是"多對一" 正好記憶標籤的名字
-->
<many-to-one name="customer" class="cn.ahpu.domain.Customer" column="custId"/>
<!--
<many-to-one name="" class="" column=""/>
name: 關聯一方類對象的那個屬性名
class: 屬性類類型的全路徑(給了全路徑才能幫你new呀 也因此javabean裏不用自己new)
column: 表中外鍵列名
小結:一方多方對於集合或是類對象這個特殊屬性的配置本質上都只提供了3個信息,name,column,class
-->
</class>
</hibernate-mapping>
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 開發必備四大配置 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate02</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- mysql數據庫方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 開啓綁定本地session 否則工具類的getCurrentSession那個api用不了-->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 打印sql語句以及自動更新表 -->
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 映射配置文件,需要引入映射的配置文件 -->
<mapping resource="cn/ahpu/domain/Customer.hbm.xml"/>
<mapping resource="cn/ahpu/domain/Linkman.hbm.xml"/>
</session-factory>
</hibernate-configuration>
三、測試多表之間的關聯配置效果
一對多級聯保存:
其中相對於上面最原始的配置情況:
run2 不用修改任何配置文件
run2 Customer.hbm.xml中多配置: <set ... cascade="save-update">
也即是:一方配置級聯
run2_1 僅linkman.hbm.xml中多配置:<many-to-one ... cascade="save-update"/>
也即是:多方配置級聯
run2_2 Customer.hbm.xml中多配置: <set ... cascade="save-update">
linkman.hbm.xml中多配置:<many-to-one ... cascade="save-update"/>
也即是:多方和一方都配置級聯 (雙向互相級聯保存)注:cascade="save-update" 級聯保存方式爲:有則更新 沒有則保存
每次測試完可以刪掉數據庫中的兩張表,一啓動測試(本質是調用getSession這個api)hibernate框架就會根據javabean和配置文件幫你自動創建表結構,自動生成表(hibernate強大到可以讓一個沒有學過數據庫的後端程序員也能輕鬆地操作操作數據庫,當然要是你懶不想學如何寫hibernate的xml配置文件,像胡平那樣先建好表然後通過eclipse反向工程來反向生成xml配置文件,就無法體會到這種強大了)
TestSave.java
package cn.ahpu.test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import cn.ahpu.domain.Customer;
import cn.ahpu.domain.Linkman;
import cn.ahpu.utils.HibernateUtils;
/**
* 測試一對多 保存
* @author 軟件163韓竹安
* 2019年12月23日-下午4:36:20
*/
public class TestSave {
/**
* 方法1:最麻煩的雙向關聯方式,保存數據(就是手動調用get/set方法將對方設置進來)
* 優點:不用再動配置文件
* 缺點:手動set/get 麻煩
*/
@Test
public void run1() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
//添加客戶"天河" 他有兩個聯繫人 "天河爸" "天河媽" 也一併添加進來
Customer c = new Customer();
c.setCustName("天河");
Linkman l1 = new Linkman();
l1.setLkmName("天河媽");
Linkman l2 = new Linkman();
l2.setLkmName("天河爸");
//雙向關聯:手動雙向添加
c.getLinkmans().add(l1);
c.getLinkmans().add(l2);
l1.setCustomer(c);
l2.setCustomer(c);
//保存客戶
session.save(c);
session.save(l1);
session.save(l2);
tr.commit();
session.close();
}
/**
* 方法2.0:單向關聯,保存數據。就是級聯保存 (一方一個個添加多方,多方會自動保存一方)
* 優點:一方保存多方即可,不用雙向保存
* 缺點:多配置一個級聯保存
*
* 當然也可以多方保存一方,然後只保存多方,一方自動保存。級聯保存有方向性
* 此處保存customer,級聯linkman,casecade配置到customer(一方)那邊
* <set>配置cascade屬性 <set ... cascade="save-update">
*/
@Test
public void run2() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
//添加客戶"天河" 他有兩個聯繫人 "天河爸" "天河媽" 也一併添加進來
Customer c = new Customer();
c.setCustName("天河");
Linkman l1 = new Linkman();
l1.setLkmName("天河媽");
Linkman l2 = new Linkman();
l2.setLkmName("天河爸");
//單向關聯:保存其中一邊即可
c.getLinkmans().add(l1);
c.getLinkmans().add(l2);
//保存客戶 (不需要保存聯繫人了)
session.save(c);
tr.commit();
}
/**
* 方法2.1: 單向關聯,保存數據。級聯保存
* 保存linkamn(多方) 級聯customer(一方)
*
* 此處加linkman的配置: <many-to-one ... cascade="save-update"/> (記得測試前先刪掉customer的級聯配置)
*/
@Test
public void run2_1() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
//添加客戶"天河" 他有兩個聯繫人 "天河爸" "天河媽" 也一併添加進來
Customer c = new Customer();
c.setCustName("天河");
Linkman l1 = new Linkman();
l1.setLkmName("天河媽");
Linkman l2 = new Linkman();
l2.setLkmName("天河爸");
//單向關聯:保存其中一邊即可 保存誰 誰主動去添加另一方
l1.setCustomer(c);
l2.setCustomer(c);
//保存聯繫人 (不需要保存客戶了)
session.save(l1);//save 此時數據庫中沒有c1 保存customer表
session.save(l2);//update 此時數據庫中有c1 更新customer表(此處表太簡單,不需要修改啥,但絕對避免了插入兩條一樣的記錄)
tr.commit();
}
/**
* 方法2.2: 單向關聯,保存數據。級聯保存
* 雙方各保存一次 兩邊都配置級聯
*
* linkman.hbm.xml和customer.hbm.xml都配置cascade="save-update"
*/
@Test
public void run2_2() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
//添加客戶"天河" 他有兩個聯繫人 "天河爸" "天河媽" 也一併添加進來
Customer c = new Customer();
c.setCustName("天河");
Linkman l1 = new Linkman();
l1.setLkmName("天河媽");
Linkman l2 = new Linkman();
l2.setLkmName("天河爸");
//單向關聯:保存其中一邊即可 保存誰 誰主動去添加另一方
l1.setCustomer(c);//下面保存l1同時保存了c
c.getLinkmans().add(l2);//c已經在數據庫中了 操作javabean=操作表 l2也會被保存到表中 因爲雙方都配置了級聯 相互級聯
session.save(l1);
tr.commit();
}
//小結:方法2.0最好用 也即是:一方配置級聯 保存一方 級聯多方 (然而保存數據你卻不知道是先錄入的一方,還是先錄入的多方,所以都得掌握)
}
我想,一般情況下,就保存而言,雙向都配置級聯保存“casecade=“save-update””吧 這樣保存任何一方,另一方要是沒有的話都會級聯保存,新插入沒有的記錄。所以我就都留着
默認配置更新1:此時默認配置變成更新1,就是雙方都加個級聯保存,都只多配置一個cascade屬性
customer.hbm.xml
linkman.hbm.xml
一對多級聯刪除:
delete0: 默認配置不變
delete1: customer.hbm.xml級聯里加個屬性值delete:<set ... cascade="..,delete">
deleteX: 相對於默認配置更新1: 僅linkman.hbm.xml配置了級聯刪除: <many-to-one ... cascade="save-update,delete"/>
deleteY: 雙方都配置了級聯刪除,刪任何一條數據,哪怕是聯繫人,相互級聯,整個數據庫空了
TestDelete.java
package cn.ahpu.test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import cn.ahpu.domain.Customer;
import cn.ahpu.domain.Linkman;
import cn.ahpu.utils.HibernateUtils;
/**
* 測試一對多 級聯刪除
* @author 軟件163韓竹安
* 2019年12月23日-下午6:04:22
*/
public class TestDelete {
// 導入數據
@Test
public void importDate() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
// 添加客戶"天河" 他有兩個聯繫人 "天河爸" "天河媽" 也一併添加進來
Customer c = new Customer();
c.setCustName("天河");
Linkman l1 = new Linkman();
l1.setLkmName("天河媽");
Linkman l2 = new Linkman();
l2.setLkmName("天河爸");
// 單向關聯:保存其中一邊即可
c.getLinkmans().add(l1);
c.getLinkmans().add(l2);
// 保存客戶 (不需要保存聯繫人了)
session.save(c);
tr.commit();
}
/**
* hibernate正常刪除 默認情況:
* 刪除誰就只刪除誰,且一定能刪除成功,不會多刪除與之有關聯的數據,也不會報錯說刪不了
* eg:刪除客戶,會只刪除這個客戶,不會刪除下面的聯繫人,也不會報錯說你這個客戶被某些聯繫人外鍵關聯了而刪不了
* 原因:避免外鍵衝突即可,hibernate會事先將此客戶下的所有聯繫人的外鍵置爲null,再刪除就沒衝突了(可以通過打印的sql語句察覺到這一點)
*/
@Test
public void delete0() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
Customer c = session.get(Customer.class, 1L);
session.delete(c);
tr.commit();
}
/**
* 刪除一方,級聯刪除旗下所有的多方
* eg:一般一方刪除了,多方數據沒必要保留 eg:刪除部門,部門下的員工沒有存在的必要的 | 刪除客戶,不需要聯繫他了,旗下的聯繫人也沒必要還留在數據庫了
* 在一方級聯配置里加個刪除delete
* 本來是: <set name="linkmans" cascade="save-update">
* 加個配置改成: <set name="linkmans" cascade="save-update,delete"> 即配置級聯刪除
* 刪除客戶,級聯刪除其下的所有聯繫人
*/
@Test
public void delete1() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
Customer c = session.get(Customer.class, 1L);
session.delete(c);
tr.commit();
}
/*******************************下面兩個配置僅做了解,不需要會,實際開發用不到*****************************/
/**
* 瞭解,沒必要學,一般用不到
* 類上面的配置:先刪除customer.hbm.xml裏的級聯刪除屬性(不刪的話待會兒兩邊都級聯刪除,兩邊相互作用可能兩張表的數據全沒了),
* 轉而在linkman.hbm.xml里加上級聯刪除cascade="..,delete"
* 此時若刪除,聯繫人,同樣會其所屬的客戶(其他聯繫人的客戶外鍵變爲null)
* 瞭解即可,基本用不到
*/
@Test
public void deleteX() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
Linkman l = session.get(Linkman.class, 1L);
session.delete(l);
tr.commit();
}
/**
* 瞭解,沒必要學,肯定用不到
* 兩邊都配置級聯刪除,這樣刪除一個聯繫人會刪除對應客戶,而客戶也配置了級聯,被刪除時又去級聯刪除其下所有的聯繫人,
* 整個聯繫人客戶羣體全沒了,明顯不合理
*
* 此時雙方都配置了級聯刪除
*/
@Test
public void deleteY() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
Linkman l = session.get(Linkman.class, 1L);
session.delete(l);
tr.commit();
}
}
一般一方刪除,多方也級聯刪除比較合理,而多方刪除一條記錄,一方與其他多方不受影響,這樣的配置最科學,也即是再加上一方的級聯刪除配置,多方配置不懂
配置更新2:
customer.hbm.xml:
linkman.hbm.xml:相對於更新1不變
拓展瞭解:
下面更多的瞭解下即可(考試肯定不會考,ssh框架在國內也已經過時了,主要學設計思想):
cascade屬性的一些常見取值:
* none -- 不使用級聯
* save-update -- 級聯保存或更新
* delete -- 級聯刪除
* delete-orphan -- 孤兒刪除.(注意:只能應用在一對多關係)
* all -- 等價於"save-update,delete"
* all-delete-orphan -- 等價於"save-update delete delete-orphan"
孤兒刪除(孤子刪除)(只有在一對多的環境下才有孤兒刪除)
* 在一對多的關係中,可以將一的一方認爲是父方.將多的一方認爲是子方.孤兒刪除:在解除了父子關係的時候.將子方記錄就直接刪除。(具體表現爲在set集合中刪除一個子方 本來子方應該只是數據庫中的外鍵字段被置爲null了,但孤兒刪除報復心態嚴重,和我解除關係不是簡單地外鍵字段=null,而是把你給弄死(直接從數據庫中刪了))
簡言之:多方表的外鍵爲null的記錄會被hibernate自動給刪除
* <many-to-one cascade="delete-orphan" />
有時讓其中一方(尤其多對多)放棄外鍵約束 inverse="true" (瞭解)
cascade和inverse的區別
cascade用來級聯操作(保存、修改和刪除) ★保存和刪除數據的
inverse用來維護外鍵的
多對多,一般設計爲具體中間實體,變成兩個一對多,hibernate的一對多就不深究了
項目文件下載
今日項目文件下載:直接下載hibernate02.zip 網盤備份下載
最後的配置 今天無非學個級聯,詳細琢磨了下各種級聯的效果罷了。。完全不需要
<set name="linkmans" cascade="save-update,delete">
<key column="custId"/>
<one-to-many class="cn.ahpu.domain.Linkman"/>
</set>
<many-to-one name="customer" class="cn.ahpu.domain.Customer" column="custId" cascade="save-update"/>
小結:
save-update,保存此方後,自動保存(已近有就不用了,必要時更新下)此方的javabean對象主動set,add而關聯的另一方的記錄
delete:級聯刪除,只要加上,刪除了此類的一條記錄,與之關聯的另一方就會被級聯刪除(千萬不能雙方都配置級聯,頂多一方配置個,否則相互級聯,一刪全刪)