一、 一對多的ORM關(guān)系映射
ORM : Object Relational Mapping 。 對象 關(guān)系 映射。
- 首先完成Relational數(shù)據(jù)庫表的一對多的關(guān)系
- 完成Object實(shí)體對象的一對多關(guān)系
- 完成Mapping映射文件中一對多關(guān)系的映射配置
一對多的實(shí)例:客戶-聯(lián)系人
一個客戶(公司)---對應(yīng)---多個聯(lián)系人
步驟:
- 創(chuàng)建兩個數(shù)據(jù)庫表customer,linkman,在多的一方添加外鍵建立關(guān)系
- 創(chuàng)建兩個實(shí)體類
Customer和Linkman
,在實(shí)體類中分別建立與對方的關(guān)系 - 創(chuàng)建兩個實(shí)體類對應(yīng)的配置文件,分別配置完成一對多的關(guān)系映射
1.導(dǎo)入jar包(省略)
導(dǎo)入Hibernate所需要的一些jar包。
2. 創(chuàng)建數(shù)據(jù)庫表customer與linkman,并建立聯(lián)系。R
創(chuàng)建customer表與linkman表,在多的一方linkman的表中添加外鍵,指向一的一方的主鍵
create database hibernate_day03;
use hibernate_day03;
客戶表 :
CREATE TABLE `cst_customer` (
`cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客戶編號(主鍵)',
`cust_name` varchar(32) NOT NULL COMMENT '客戶名稱(公司名稱)',
`cust_user_id` bigint(32) DEFAULT NULL COMMENT '負(fù)責(zé)人id',
`cust_create_id` bigint(32) DEFAULT NULL COMMENT '創(chuàng)建人id',
`cust_source` varchar(32) DEFAULT NULL COMMENT '客戶信息來源',
`cust_industry` varchar(32) DEFAULT NULL COMMENT '客戶所屬行業(yè)',
`cust_level` varchar(32) DEFAULT NULL COMMENT '客戶級別',
`cust_linkman` varchar(64) DEFAULT NULL COMMENT '聯(lián)系人',
`cust_phone` varchar(64) DEFAULT NULL COMMENT '固定電話',
`cust_mobile` varchar(16) DEFAULT NULL COMMENT '移動電話',
PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
聯(lián)系人數(shù)據(jù)庫表 :
CREATE TABLE `cst_linkman` (
`lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '聯(lián)系人編號(主鍵)',
`lkm_name` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人姓名',
`lkm_cust_id` bigint(32) NOT NULL COMMENT '客戶id',
`lkm_gender` char(1) DEFAULT NULL COMMENT '聯(lián)系人性別',
`lkm_phone` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人辦公電話',
`lkm_mobile` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人手機(jī)',
`lkm_email` varchar(64) DEFAULT NULL COMMENT '聯(lián)系人郵箱',
`lkm_qq` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人qq',
`lkm_position` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人職位',
`lkm_memo` varchar(512) DEFAULT NULL COMMENT '聯(lián)系人備注',
PRIMARY KEY (`lkm_id`),KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
)
3. 創(chuàng)建Customer與Linkman實(shí)體類,并建立聯(lián)系。O
分別在兩個實(shí)體類中添加與另一個實(shí)體類的聯(lián)系。
Customer的實(shí)體類:
Linkman的實(shí)體類:
4. 在對應(yīng)的映射文件中添加一對多的映射關(guān)系
Customer.hbm.xml映射文件:
<!-- 集合,一對多關(guān)系,在映射文件中配置 -->
<!--
name屬性:集合屬性名
column屬性: 外鍵列名(通過這個外鍵找到Linkman放入集合)
class屬性: 與我關(guān)聯(lián)的對象完整類名(從哪里找)
-->
<set name="linkemans">
<key column="lkm_cust_id"></key>
<one-to-many class="com.itdream.domain.Linkeman"/>
</set>
Linkman.hbm.xml映射文件:
<!-- 多對一配置 -->
<!--
name屬性:引用的屬性名
column屬性: 外鍵列名(通過外鍵找到這個引用)
class屬性: 與我關(guān)聯(lián)的對象完整類名(從哪找)
-->
<many-to-one name="customer" column="lkm_cust_id" class="com.itdream.domain.Customer"></many-to-one>
5.在核心配置文件中加載映射文件
<?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的核心配置文件 -->
<hibernate-configuration>
<session-factory>
<!-- 1.Hibernate連接數(shù)據(jù)庫的基本配置 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_day03</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- 2.配置與當(dāng)前線程綁定的Session對象 -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 3.Hibernate的普通配置 -->
<!-- Hibernate的方言配置,根據(jù)配置生成對應(yīng)SQL語句.這實(shí)現(xiàn)了Hibernate跨數(shù)據(jù)庫.更換數(shù)據(jù)庫只需要修改這里的配置即可-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 顯示sql語句 -->
<property name="hibernate.show_sql">true</property>
<!-- 格式化sql語句 -->
<property name="hibernate.format_sql">true</property>
<!-- Hibernate自動創(chuàng)建數(shù)據(jù)庫表的方式 hbm2ddl-->
<property name="hibernate.hbm2ddl.auto">none</property>
<!-- 設(shè)置隔離級別:4:Repeatable read可重復(fù)讀 -->
<property name="hibernate.isolation">4</property>
<!-- c3p0連接池配置-->
<property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
<!-- 最小連接 -->
<property name="hibernate.c3p0.min_size">5</property>
<!-- 最大連接數(shù) -->
<property name="hibernate.c3p0.max_size">20</property>
<!-- 連接超時時長 -->
<property name="hibernate.c3p0.timeout">120</property>
<!-- 每120秒檢查空閑連接 -->
<property name="hibernate.c3p0.idle_test_period">120</property>
<!-- 最大statments數(shù)量 -->
<property name="hibernate.c3p0.max_statements">120</property>
<!-- 連接用完后,每次增加的連接數(shù) -->
<property name="hibernate.c3p0.acquire_increment">2</property>
<!-- 每次都驗(yàn)證連接是否可用 -->
<property name="hibernate.c3p0.validate">false</property>
<!-- 4.加載映射配置 -->
<mapping resource="com/itdream/domain/Customer.hbm.xml"/>
<mapping resource="com/itdream/domain/Linkman.hbm.xml"/>
</session-factory>
</hibernate-configuration>
6.Hibernate:1對多關(guān)系測試
/**
* 測試數(shù)據(jù)庫表的1對多關(guān)系:
*/
@Test
//保存一個客戶與兩個聯(lián)系人
public void test() {
//獲取Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//------------------------------------------
//1> 創(chuàng)建一個客戶
Customer customer = new Customer();
customer.setCust_name("阿里巴巴");
//2> 創(chuàng)建兩個聯(lián)系人
Linkman linkeman = new Linkman();
linkeman.setLkm_name("小馬");
Linkman linkeman2 = new Linkman();
linkeman2.setLkm_name("小云");
//3> 表達(dá)一對多,客戶下有多個聯(lián)系人
customer.getLinkemans().add(linkeman);
customer.getLinkemans().add(linkeman2);
//4> 表達(dá)多對一,聯(lián)系人屬于哪個客戶
linkeman.setCustomer(customer);
linkeman2.setCustomer(customer);
//5> 保存.將瞬時態(tài)對象轉(zhuǎn)換成持久態(tài)對象
session.save(customer);
session.save(linkeman);
session.save(linkeman2);
//------------------------------------------
//提交事務(wù)
transaction.commit();
}
@Test
//為客戶1添加一個聯(lián)系人
public void test2() {
//獲取Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//------------------------------------------
//1> 獲取客戶1
Customer customer = session.get(Customer.class, 1L);
//2> 創(chuàng)建待添加的聯(lián)系人
Linkman linkeman = new Linkman();
linkeman.setLkm_name("劉總");
//3> 建立關(guān)系:表達(dá)一對多,客戶下有多個聯(lián)系人
customer.getLinkemans().add(linkeman);
//4> 建立關(guān)系:表達(dá)多對一,聯(lián)系人屬于哪個客戶
linkeman.setCustomer(customer);
//5> 保存.將瞬時態(tài)對象轉(zhuǎn)換成持久態(tài)對象
//session.save(customer);(customer是持久態(tài)對象無需手動保存,會自動更新數(shù)據(jù)庫)
session.save(linkeman);
//------------------------------------------
//提交事務(wù)
transaction.commit();
}
@Test
//為客戶1刪除一個聯(lián)系人劉總
public void test3() {
//獲取Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//------------------------------------------
//1> 獲取客戶1
Customer customer = session.get(Customer.class, 1L);
//2> 獲取待刪除的聯(lián)系人
Linkman linkeman = session.get(Linkman.class, 3L);
//3> 建立關(guān)系:從客戶的聯(lián)系人集合中刪除該聯(lián)系人
customer.getLinkemans().remove(linkeman);
//4> 建立關(guān)系:從聯(lián)系人的客戶引用中刪除該客戶
linkeman.setCustomer(null);
//5> 保存.將瞬時態(tài)對象轉(zhuǎn)換成持久態(tài)對象(customer與linkman都是持久態(tài)對象,無需保存會自動更新)
//session.save(customer);
//session.save(linkeman);
//------------------------------------------
//提交事務(wù)
transaction.commit();
}
上面在操作瞬時態(tài)對象時,每個瞬時態(tài)對象都要手動進(jìn)行save保存,為了節(jié)省代碼,Hibernate提供了級聯(lián)操作。只要設(shè)置了級聯(lián)的這一方添加了級聯(lián)配置,與它產(chǎn)生關(guān)系的另一方就無需在進(jìn)行save或者update等操作了。
級聯(lián)測試:
在客戶的一端設(shè)置級聯(lián)保存,那么只要客戶是持久化狀態(tài),就無需對與其產(chǎn)生關(guān)系的聯(lián)系人進(jìn)行save或者update操作了.
<!--
級聯(lián)操作: cascade
save-update: 級聯(lián)保存更新
delete:級聯(lián)刪除
all:save-update+delete
級聯(lián)操作: 簡化操作.目的就是為了少些兩行代碼.
-->
<set name="linkemans" cascade="save-update">
<key column="lkm_cust_id"></key>
<one-to-many class="com.itdream.domain.Linkman"/>
</set>
@Test
//保存一個客戶與兩個聯(lián)系人(級聯(lián)保存)
//為了簡化代碼操作,在做session.save時,save客戶就可以同時將客戶中的ecustomer對象保存.
//但是,如果直接省略,會報(bào)錯:瞬時態(tài)與執(zhí)行態(tài)關(guān)聯(lián)的錯誤.因此在需要做級聯(lián)保存的映射文件中配置,將cascad屬性設(shè)為save-update即可
public void test1() {
//獲取Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//------------------------------------------
//1> 創(chuàng)建一個客戶
Customer customer = new Customer();
customer.setCust_name("阿里巴巴");
//2> 創(chuàng)建兩個聯(lián)系人
Linkman linkeman = new Linkman();
linkeman.setLkm_name("小馬");
Linkman linkeman2 = new Linkman();
linkeman2.setLkm_name("小云");
//3> 建立關(guān)系:將聯(lián)系人添加到客戶的集合中
customer.getLinkemans().add(linkeman);
customer.getLinkemans().add(linkeman2);
//4> 建立關(guān)系:將客戶添加到聯(lián)系人中
linkeman.setCustomer(customer);
linkeman2.setCustomer(customer);
//5> 保存.將瞬時態(tài)對象轉(zhuǎn)換成持久態(tài)對象
session.save(customer);
//在customer的映射文件中配置級聯(lián)保存,在保存customer的時候會自動保存與它產(chǎn)生關(guān)聯(lián)的聯(lián)系人
// session.save(linkeman);
// session.save(linkeman2);
//------------------------------------------
//提交事務(wù)
transaction.commit();
}
@Test
//為添加一個聯(lián)系人(級聯(lián)保存)
//為了簡化代碼操作,在做session.save時,save客戶就可以同時將客戶中的ecustomer對象保存.
//但是,如果直接省略,會報(bào)錯:瞬時態(tài)與執(zhí)行態(tài)關(guān)聯(lián)的錯誤.因此在需要做級聯(lián)保存的映射文件中配置,將cascad屬性設(shè)為save-update即可
public void test2() {
//獲取Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//------------------------------------------
//1> 獲取客戶1
Customer customer = session.get(Customer.class, 1L);
//2> 創(chuàng)建一個聯(lián)系人
Linkman linkeman = new Linkman();
linkeman.setLkm_name("小白");
//3> 建立關(guān)系:將聯(lián)系人添加到客戶的集合中
customer.getLinkemans().add(linkeman);
//4> 建立關(guān)系:將客戶添加到聯(lián)系人中
linkeman.setCustomer(customer);
//5> 保存.customer通過get方法取出就是持久態(tài)了,無需手動保存
//因?yàn)榕渲昧思壜?lián)保存,保存customer的時候Hibernate會自動將與這個customer產(chǎn)生關(guān)系的linkman設(shè)為持久態(tài)保存
//session.save(customer);
//------------------------------------------
//提交事務(wù)
transaction.commit();
}
@Test
//為客戶1刪除聯(lián)系人id為1的小馬,增加一個聯(lián)系人為小宋
public void test4() {
//獲取Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//-------------------------------
//實(shí)現(xiàn)業(yè)務(wù)邏輯 : 為客戶1刪除聯(lián)系人id為1的小馬,增加一個聯(lián)系人為小宋
//1> 獲取客戶1
Customer customer = session.get(Customer.class, 1L);
//2> 獲取要刪除的聯(lián)系人小馬
Linkman linkman = session.get(Linkman.class, 1L);
//3> 創(chuàng)建一個聯(lián)系人小宋
Linkman linkman2 = new Linkman();
linkman2.setLkm_name("小宋");
//4> 建立關(guān)系: 在客戶1方建立對聯(lián)系人的關(guān)系
//從聯(lián)系人集合中刪除要刪除的聯(lián)系人
customer.getLinkemans().remove(linkman);
//添加要添加的聯(lián)系人到聯(lián)系人集合
customer.getLinkemans().add(linkman2);
//5> 建立關(guān)系: 在聯(lián)系人放建立對客戶1的關(guān)系
//解除客戶在聯(lián)系人的引用
linkman.setCustomer(null);
//在添加的聯(lián)系人的客戶引用中添加客戶1
linkman2.setCustomer(customer);
//6> 保存
//customer是持久態(tài),刪除的聯(lián)系人也是持久態(tài),待添加的聯(lián)系人不是持久態(tài),但是由于customer設(shè)置了級聯(lián)保存因此也無需save保存
//-------------------------------
//提交事務(wù)
transaction.commit();
//關(guān)閉資源,getCurrentSession方法獲得與線程綁定的Session對象無需手動關(guān)閉
}
@Test
//級聯(lián)刪除 : 在實(shí)際開發(fā)中基本不用,這里測試使用
//目的:在刪除聯(lián)系人的時候?qū)⑺目蛻粢矂h除掉
public void test5() {
//獲取Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//-------------------------------
//實(shí)現(xiàn)業(yè)務(wù)邏輯 : 刪除聯(lián)系人1的時候?qū)⑺目蛻粢矂h除掉
//1> 獲取要刪除的聯(lián)系人
Linkman linkman = session.get(Linkman.class, 1L);
//2> 刪除聯(lián)系人
session.delete(linkman);
//3> 保存
//linkman是持久態(tài),無需手動保存,同時它設(shè)置了級聯(lián)刪除,因此刪除聯(lián)系人的同時,會將它對應(yīng)的客戶1也刪除掉
//如果這個時候在客戶的一方也設(shè)置級聯(lián)刪除,會導(dǎo)致刪除一個客戶或者刪除一個聯(lián)系人,會將與它有關(guān)聯(lián)的所有數(shù)據(jù)都刪除掉
//-------------------------------
//提交事務(wù)
transaction.commit();
//關(guān)閉資源,getCurrentSession方法獲得與線程綁定的Session對象無需手動關(guān)閉
}
上面通過級聯(lián)的配置,節(jié)省了一些多余代碼的操作,但是我們通過查看Hibernate打印的sql語句發(fā)現(xiàn)(例如執(zhí)行test1的代碼),Hibernate會對外鍵lkm_cst_id
進(jìn)行多余的兩次操作.Linkman自身在insert添加新聯(lián)系人時已經(jīng)維護(hù)了外鍵lkm_cst_id
,那么下面update進(jìn)行的維護(hù)很顯然就是客戶customer
在進(jìn)行維護(hù),這是很冗余的操作。因此我們需要通過配置進(jìn)行優(yōu)化,Hibernate提供了inverse
的屬性來設(shè)置。
<!-- inverse屬性: 配置關(guān)系是否維護(hù).
true: customer不維護(hù)關(guān)系
false(默認(rèn)值): customer維護(hù)關(guān)系
inverse屬性: 性能優(yōu)化.提高關(guān)系維護(hù)的性能.
原則: 無論怎么放棄,總有一方必須要維護(hù)關(guān)系.
一對多關(guān)系中: 一的一方放棄.也只能一的一方放棄.多的一方不能放棄.
-->
<set name="linkemans" cascade="save-update" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class="com.itdream.domain.Linkman"/>
</set>
//操作進(jìn)階--關(guān)系維護(hù)屬性
public class Demo3 {
@Test
//保存客戶 以及客戶 下的聯(lián)系人
public void fun1(){
//1 獲得session
Session session = HibernateUtils.openSession();
//2 開啟事務(wù)
Transaction tx = session.beginTransaction();
//-------------------------------------------------
//3操作
Customer c = new Customer();
c.setCust_name("阿里巴巴");
LinkMan lm1 = new LinkMan();
lm1.setLkm_name("小馬");
LinkMan lm2 = new LinkMan();
lm2.setLkm_name("小云");
//表達(dá)一對多,客戶下有多個聯(lián)系人.
// 如果客戶放棄維護(hù)與聯(lián)系人的關(guān)系. 維護(hù)關(guān)系的代碼可以省略
//c.getLinkMens().add(lm1);
//c.getLinkMens().add(lm2);
//表達(dá)多對多,聯(lián)系人屬于哪個客戶
lm1.setCustomer(c);
lm2.setCustomer(c);
//如果Linkman的配置文件中配置了級聯(lián)保存,則這條保存客戶的語句可以省略
//session.save(c);
session.save(lm1);
session.save(lm2);
//-------------------------------------------------
//4提交事務(wù)
tx.commit();
//5關(guān)閉資源
session.close();
}
上面的配置表示:customer放棄外鍵維護(hù)lkm_cst_id
.那么同樣再執(zhí)行test1的代碼時,下面update
的冗余的sql語句就消失了,達(dá)到了優(yōu)化的目的.
二、 多對多的ORM關(guān)系映射
ORM : Object Relational Mapping 。 對象 關(guān)系 映射。
- 首先完成Relational數(shù)據(jù)庫表的多對多的關(guān)系表達(dá)(外鍵)
- 完成Object實(shí)體對象的多對多關(guān)系表達(dá)(分別創(chuàng)建Set集合存儲對方的對象)
- 完成Mapping映射文件中一對多關(guān)系的映射配置(配置)
多對多的實(shí)例
用戶與角色(崗位) User & Role
一個用戶可以在公司中擔(dān)任多個崗位
一個崗位上也有多個員工用戶在工作
1.jar包上面一對多的例子已經(jīng)導(dǎo)入
2.創(chuàng)建多對多的兩個數(shù)據(jù)庫表,進(jìn)行關(guān)系表達(dá) Relational
多對多的數(shù)據(jù)庫多表關(guān)系表達(dá),需要創(chuàng)建一個第三方表,這個第三方表至少有兩個字段,分別指向兩張表的主鍵.
-- 用戶表
CREATE TABLE `sys_user` (
`user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用戶id',
`user_code` varchar(32) NOT NULL COMMENT '用戶賬號',
`user_name` varchar(64) NOT NULL COMMENT '用戶名稱',
`user_password` varchar(32) NOT NULL COMMENT '用戶密碼',
`user_state` char(1) NOT NULL COMMENT '1:正常,0:暫停',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
-- 用戶角色表.
CREATE TABLE `sys_role` (
`role_id` bigint(32) NOT NULL AUTO_INCREMENT,
`role_name` varchar(32) NOT NULL COMMENT '角色名稱',
`role_memo` varchar(128) DEFAULT NULL COMMENT '備注',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- 第三方表,負(fù)責(zé)維護(hù)user與role表的關(guān)系
CREATE TABLE `sys_user_role` (
`role_id` bigint(32) NOT NULL COMMENT '角色id',
`user_id` bigint(32) NOT NULL COMMENT '用戶id',
PRIMARY KEY (`role_id`,`user_id`),
KEY `FK_user_role_user_id` (`user_id`),
CONSTRAINT `FK_user_role_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_user_role_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
3.創(chuàng)建多對多的兩個實(shí)體類,進(jìn)行關(guān)系表達(dá) Object
/**
* 持久化類:用戶實(shí)體類
*/
public class User {
// 普通屬性
private Long user_id;//外鍵
private String user_code;
private String user_name;
private String user_password;
private Character user_state;
// 建立多對多關(guān)系:Set集合存儲當(dāng)前User對象持有的Role角色信息
private Set<Role> roles = new HashSet<>();
/**
* 持久化類:用戶角色的實(shí)體類
*/
public class Role {
// 普通屬性
private Long role_id;//外鍵
private String role_name;
private String role_memo;
// 建立實(shí)體類多對多關(guān)系:Set集合存儲屬于該Role角色的所有User對象
private Set<User> users = new HashSet<>();
4.配置兩個映射文件,進(jìn)行關(guān)系表達(dá) Mapping
User.hbm.xml映射文件配置多對多關(guān)系:
<!-- 多對多的關(guān)系映射配置 -->
<!--
name: 集合屬性名
table: 配置中間表名
key
|-column:外鍵,"我"的外鍵列名
class: 我與哪個類是多對多關(guān)系
column:外鍵.與我產(chǎn)生多對多關(guān)系的外鍵列名
-->
<set name="roles" table="sys_user_role">
<key column="user_id"></key>
<many-to-many class="com.itdream.domain.Role" column="role_id" ></many-to-many>
</set>
------------------------------------------------------------
Role.hbm.xml映射文件配置多對多關(guān)系:
<!-- 多對多的關(guān)系映射配置 -->
<!--
name: 集合屬性名
table: 配置中間表名
key
|-column:外鍵,"我"的外鍵列名
class: 我與哪個類是多對多關(guān)系
column:外鍵.與我產(chǎn)生多對多關(guān)系的外鍵列名
-->
<set name="roles" table="sys_user_role">
<key column="user_id"></key>
<many-to-many class="com.itdream.domain.Role" column="role_id" ></many-to-many>
</set>
4.在hibernate.cfg.xml
中加載映射文件:
<mapping resource="com/itdream/domain/User.hbm.xml"/>
<mapping resource="com/itdream/domain/Role.hbm.xml"/>
4.Hibernate多對多關(guān)系測試
/**
* Hibernate多對多關(guān)系測試
*/
@Test
//多對多關(guān)系:創(chuàng)建兩個用戶,創(chuàng)建三個角色由兩個用戶分配保存
public void test1() {
//獲取Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//----------------------------
//實(shí)現(xiàn)業(yè)務(wù)邏輯
//1> 創(chuàng)建兩個用戶
User user = new User();
User user2 = new User();
user.setUser_name("楊冪");
user2.setUser_name("唐嫣");
//2> 創(chuàng)建三個角色(崗位)
Role role = new Role();
Role role2 = new Role();
Role role3 = new Role();
role.setRole_name("明星");
role2.setRole_name("女兒");
role.setRole_name("妻子");
//3> 用戶表達(dá)關(guān)系:分別在用戶中添加角色
//楊冪持有明星,女兒,妻子三個角色
user.getRoles().add(role);
user.getRoles().add(role2);
user.getRoles().add(role3);
//唐嫣持有明星和女兒兩個角色
user2.getRoles().add(role);
user2.getRoles().add(role2);
//4> 角色表達(dá)關(guān)系:分別在角色中添加用戶
role.getUsers().add(user);
role2.getUsers().add(user);
role3.getUsers().add(user);
role.getUsers().add(user2);
role2.getUsers().add(user2);
//5> 保存
session.save(user);
session.save(user2);
session.save(role);
session.save(role2);
session.save(role3);
//----------------------------
//提交事務(wù)
transaction.commit();
}
==================================================
我們按照非常正規(guī)的方式,分別建立了user和role對象然后逐個保存,但是就是這么看起來正確的操作卻報(bào)錯了。
org.hibernate.exception.ConstraintViolationException: could not execute statement
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Duplicate entry '1-1' for key 'PRIMARY'
根據(jù)日志錯誤信息,我們知道產(chǎn)生了重復(fù)的主鍵:1-1.
這是因?yàn)槲覀兊牡谌奖碇挥袃蓚€字段,就是兩張表的外鍵,這張表的主鍵就是兩個外鍵組成的聯(lián)合主鍵。
那為什么會產(chǎn)生重復(fù)的主鍵1-1呢?
我們在上面inverse外鍵維護(hù)關(guān)系的屬性時講了,inverse默認(rèn)值是false,默認(rèn)維護(hù)外鍵.
我們在建立關(guān)系的時候,user一方建立了關(guān)系-getRoles.add(user).role一方也建立了關(guān)系-getUsers.add(role);
這樣它們在維護(hù)外鍵時會導(dǎo)致user維護(hù)了外鍵往第三方表插入了1 1. role在維護(hù)外鍵時也會往第三方表插入1 1.導(dǎo)致聯(lián)合主鍵重復(fù)了。
======================================================
要解決這一問題,有兩種方式:
方式1: 只讓一方建立關(guān)系
即: 注釋掉一方的建立關(guān)系,例如注釋掉role方建立與user的關(guān)系表達(dá)
將 role.getUsers.add(role)的語句全都注釋掉就可以了。
但是這種方式,我們看起來不是很舒服,因?yàn)楦杏X不夠符合邏輯。
建立關(guān)系應(yīng)該是兩方都建立。因此就可以使用方式2.
--------------------------------------------------------
方式2: 讓被動的一方放棄外鍵維護(hù)
在這里因?yàn)閡ser是主動選擇的role角色,因此讓role放棄外鍵維護(hù)
<!-- 使用inverse屬性
true: 放棄維護(hù)外鍵關(guān)系
false(默認(rèn)值):維護(hù)關(guān)系
結(jié)論: 將來在開發(fā)中,如果遇到多對多關(guān)系.一定要選擇一方放棄維護(hù)關(guān)系.
一般誰來放棄要看業(yè)務(wù)方向(誰處于被動狀態(tài)). 例如錄入員工時,需要為員工指定所屬角色,角色是被動的.
那么業(yè)務(wù)方向就是由員工維護(hù)角色. 角色不需要維護(hù)與員工關(guān)系.角色放棄維護(hù)。
-->
<set name="users" table="sys_user_role" inverse="true" >
<key column="role_id" ></key>
<many-to-many class="User" column="user_id" ></many-to-many>
</set>
設(shè)置級聯(lián)保存
在上面的測試中,在保存瞬時態(tài)對象到一級緩存中時,需要將每一個瞬時態(tài)對象都一一save,為了節(jié)省代碼,我們使用級聯(lián)保存來解決。
// 5> 保存
//正常操作需要save保存每一個瞬時態(tài)對象,為了節(jié)省這些代碼,我們使用級聯(lián)保存cascade
//分別在User.hbm.xml與Role.hbm.xml映射文件中設(shè)置級聯(lián)配置
<!-- 使用inverse屬性
true: 放棄維護(hù)外鍵關(guān)系
false(默認(rèn)值):維護(hù)關(guān)系
-->
Role.hbm.xml :
<set name="users" table="sys_user_role" inverse="true" >
<key column="role_id" ></key>
<many-to-many class="User" column="user_id" ></many-to-many>
</set>
User.hbm.xml :
<set name="roles" table="sys_user_role" cascade="save-update" >
<key column="user_id" ></key>
<many-to-many class="Role" column="role_id" ></many-to-many>
</set>
==================================================================
//這樣設(shè)置之后,只需要保存其中一個對象都能級聯(lián)將其它瞬時對象全都保存.(要保證所有的對象都能被關(guān)聯(lián)到)
//例如,保存user,會級聯(lián)保存與它建立關(guān)系的role,role2,role3.而role,role2,與user和uer2產(chǎn)生了關(guān)聯(lián),因此user2也成功保存
//又如,保存role3,會級聯(lián)保存與role3建立了關(guān)系的user2,user2會級聯(lián)保存role和role2,role和role2又能級聯(lián)保存user
session.save(user);
// session.save(user2);
// session.save(role);
// session.save(role2);
// session.save(role3);
為用戶添加角色:
//為唐嫣增加一個角色 : 女神
@Test
public void test2() {
//獲得Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//----------------------------------
//實(shí)現(xiàn)業(yè)務(wù)邏輯
//1> 獲取user對象唐嫣
User user = session.get(User.class, 2L);
//2> 創(chuàng)建一個新的角色:女神
Role role = new Role();
role.setRole_name("女神");
//3> user表達(dá)關(guān)系: 將新建的角色添加到user對象的角色集合中
user.getRoles().add(role);
//4> role表達(dá)關(guān)系 : 將user對象添加到role對象的用戶集合中
role.getUsers().add(user);
//5> 保存轉(zhuǎn)化為持久態(tài)
// user對象已經(jīng)是持久態(tài)了,無需手動保存,又因?yàn)樘砑恿思壜?lián)保存,所以會級聯(lián)保存role對象
//----------------------------------
//提交事務(wù)(事務(wù)提交時,數(shù)據(jù)更新到數(shù)據(jù)庫,清空一級緩存區(qū))
transaction.commit();
}
為唐嫣解除明星角色
@Test
//為唐嫣解除明星角色
public void test3() {
//獲取Session對象
Session session = HibernateUtils.getCurrentSession();
//開啟事務(wù)
Transaction transaction = session.beginTransaction();
//---------------------------------------------
//處理業(yè)務(wù)邏輯
//1> 獲取User對象唐嫣:id為2
User user = session.get(User.class, 2L);
//2> 獲取Role對象明星:id為2
Role role = session.get(Role.class, 2L);
//3> User表達(dá)關(guān)系:從role集合中移除明星角色
user.getRoles().remove(role);
//4> Role表達(dá)關(guān)系:從user集合中移除用戶唐嫣
role.getUsers().remove(user);
//5> 保存,將瞬時態(tài)對象轉(zhuǎn)換為持久態(tài)
//user與role對象已經(jīng)持久態(tài)了
//---------------------------------------------
//提交事務(wù)
transaction.commit();
}
cascade與inverse使用總結(jié):
- cascade : 實(shí)質(zhì)上是為了節(jié)省代碼操作
cascade級聯(lián)操作:
save-update: 級聯(lián)保存更新
delete:級聯(lián)刪除
all:級聯(lián)保存更新+級聯(lián)刪除
結(jié)論: cascade簡化代碼書寫.該屬性使不使用無所謂. 建議要用只用save-update.
使用delete操作太過危險(xiǎn).尤其在多對多中.不建議使用.
- inverse : 減少多余的sql語句,優(yōu)化Hibernate的性能
雙向建立關(guān)系時,雙方都進(jìn)行外鍵維護(hù)產(chǎn)生了多余的sql語句,使用inverse屬性使一方放棄外鍵維護(hù)。
使用inverse屬性
true: 放棄維護(hù)外鍵關(guān)系
false(默認(rèn)值):維護(hù)關(guān)系
結(jié)論:
在一對多關(guān)系中,通常選擇一的一方放棄外鍵維護(hù)。
多對多關(guān)系中.一定要選擇一方放棄維護(hù)關(guān)系.
一般誰來放棄要看業(yè)務(wù)方向(被動一方放棄維護(hù)). 例如錄入員工時,需要為員工指定所屬角色.
那么業(yè)務(wù)方向就是由員工維護(hù)角色. 角色不需要維護(hù)與員工關(guān)系.角色放棄維護(hù)