Hibernate一對多的關聯映射
一、數據庫表與表之間的關係
1、一對多關係
(1)什麼樣的關係是屬於一對多
一個部門可以對應多個員工,一個員工只能屬於某一個部門
一個客戶對應多個聯繫人,一個聯繫人只能屬於某一個客戶
(2)一對多建表原則:
2、多對多關係
(1)什麼樣的關係是屬於多對多
一個學生可以選擇多門課程,一門課程也可以被多個學生所選擇。
一個用戶可以選擇多個角色,一個角色也可以被多個用戶所選擇。
(2)多對多鍵表原則
3、一對一關係
(1)什麼樣的關係是屬於一對一
一個公司只能有一個註冊地址,一個註冊地址只能被一個公司註冊。
(2)一對一鍵表原則
二、實際應用(創建表與類的關係)
1、創建項目引入架包
2、創建數據庫和表
創建一對多數據庫關係的表
(1)創建客戶表
CREATE TABLE `cst_customer` (
`cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客戶編號(主鍵)',
`cust_name` varchar(32) NOT NULL COMMENT '客戶名稱(公司名稱)',
`cust_source` varchar(32) DEFAULT NULL COMMENT '客戶信息來源',
`cust_industry` varchar(32) DEFAULT NULL COMMENT '客戶所屬行業',
`cust_level` varchar(32) DEFAULT NULL COMMENT '客戶級別',
`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;
(2)創建聯繫人的表
CREATE TABLE `cst_linkman` (
`lkm_id` BIGINT(32) NOT NULL AUTO_INCREMENT COMMENT '聯繫人編號(主鍵)',
`lkm_name` VARCHAR(16) DEFAULT NULL COMMENT '聯繫人姓名',
`lkm_cust_id` BIGINT(32) DEFAULT NULL COMMENT '客戶id',
`lkm_gender` CHAR(1) DEFAULT NULL COMMENT '聯繫人性別',
`lkm_phone` VARCHAR(16) DEFAULT NULL COMMENT '聯繫人辦公電話',
`lkm_mobile` VARCHAR(16) DEFAULT NULL COMMENT '聯繫人手機',
`lkm_email` VARCHAR(64) DEFAULT NULL COMMENT '聯繫人郵箱',
`lkm_qq` VARCHAR(16) DEFAULT NULL COMMENT '聯繫人qq',
`lkm_position` VARCHAR(16) DEFAULT NULL COMMENT '聯繫人職位',
`lkm_memo` VARCHAR(512) DEFAULT NULL COMMENT '聯繫人備註',
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
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
(3)創建實體
一的一方的實體,當中放置多的一方的集合
代碼如下:
多的一方的實體,放置一的一方的對象
代碼如下:
同時分別生成set和get方法
(4)創建映射文件
在項目文件夾下創建映射文件
多的一方的映射文件
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>
<!-- name爲對應類的全路徑 table是對應的表的名稱 -->
<class name="com.itzheng.hibernate.domain.LinkMan"
table="cst_linkman">
<!-- 建立OID與主鍵的映射 -->
<id name="lkm_id" column="lkm_id">
<generator class="native">
</generator>
</id>
<!-- 建立普通屬性與表的字段的映射 -->
<property name="lkm_name" />
<property name="lkm_gender" />
<property name="lkm_phone" />
<property name="lkm_mobile" />
<property name="lkm_email" />
<property name="lkm_qq" />
<property name="lkm_position" />
<property name="lkm_memo" />
<!-- 配置多對一的關係:放置的是一的一方的對象 這裏配置的被屬於的一方 -->
<!-- many-to-one代表多對一的意思,也就是被屬於的一方,也可以理解 爲子類 -->
<!-- many-to-one 標籤
*name:一的一方的對象的屬性名稱。屬於一方的標籤
*class:一的一方類的全路徑
*column:在多的一方表的外鍵的名稱
操作customer對象就相當於操作表裏面的外鍵
-->
<many-to-one name="customer" class="com.itzheng.hibernate.domain.Customer" column="lkm_cust_id"></many-to-one>
</class>
</hibernate-mapping>
一的一方的映射文件
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="com.itzheng.hibernate.domain.Customer"
table="cst_customer">
<!-- 主鍵與類當中對象的關係,建立oid與主鍵之間的映射 -->
<id name="cust_id" column="cust_id">
<!-- 主鍵生成策略 -->
<generator class="native" />
</id>
<!-- 建立普通屬性與數據庫字段的映射 -->
<!-- 建立類中的普通的屬性和表的字段的對應 -->
<property name="cust_name" column="cust_name" />
<property name="cust_source" column="cust_source" />
<property name="cust_industry" column="cust_industry" />
<property name="cust_level" column="cust_level" />
<property name="cust_phone" column="cust_phone" />
<property name="cust_mobile" column="cust_mobile" />
<!-- 配置一對多的映射:放置的多的一方的對象的集合 -->
<!-- set標籤:
name:多的一方對應對象的集合的屬性名稱
-->
<set name="linkMans">
<!-- key標籤
*column:多的一方的外鍵的名稱
-->
<key column="lkm_cust_id"></key>
<!-- one-to-many標籤 :一對多
class屬性:多的一方類的全路徑
-->
<one-to-many class="com.itzheng.hibernate.domain.LinkMan"/>
</set>
</class>
</hibernate-mapping>
4、配置核心配置文件Customer.hbm.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:///hibernate_day03</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!-- 配置hibernate的方言的 目的是生成對應不同數據的語句 -->
<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>
<!-- 自動創建表 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 配置C3P0連接池 -->
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!--在連接池中可用的數據庫連接的最少數目 -->
<property name="c3p0.min_size">5</property>
<!--在連接池中所有數據庫連接的最大數目 -->
<property name="c3p0.max_size">20</property>
<!--設定數據庫連接的過期時間,以秒爲單位, 如果連接池中的某個數據庫連接處於空閒狀態的時間超過了timeout時間,就會從連接池中清除 -->
<property name="c3p0.timeout">120</property>
<!--每3000秒檢查所有連接池中的空閒連接 以秒爲單位 -->
<property name="c3p0.idle_test_period">3000</property>
<!-- 設置事務隔離級別 -->
<property name="hibernate.connection.isolaction">4</property>
<!-- 配置當前線程來綁定的session -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 引入兩個映射文件 -->
<mapping
resource="com/itzheng/hibernate/domain/Customer.hbm.xml" />
<mapping
resource="com/itzheng/hibernate/domain/LinkMan.hbm.xml" />
</session-factory>
</hibernate-configuration>
5、log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
# error warn info debug trace
log4j.rootLogger= info, stdout
6、配置工具類
package com.itzheng.hibernate.utils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
/*
Hibernate的工具類
做hibernate的小練習,對configuration的configure()方法很好奇,爲啥創建的對象還要執行這個方法呢。
Configuration cfg = new Configuration().configure();
原來configure()方法默認會在classpath下面尋找hibernate.cfg.xml文件,
如果沒有找到該文件,系統會打印如下信息並拋出HibernateException異常。
其實不使用configure()方法也可以
Configuration cfg = new Configuration();
這時hibernate會在classpath下面尋找hibernate.properties文件,
如果沒有找到該文件,系統會打印如下信息並拋出HibernateException異常。
*/
public class HibernateUtils {
public static final Configuration cfg;
public static final SessionFactory sf;
static {
cfg = new Configuration().configure();// 獲取與數據庫的鏈接的配置文件
sf = cfg.buildSessionFactory();//開啓事務建立與數據庫之間的鏈接
}
public static Session openSession() {
return sf.openSession();
}
public static Session getCurrentSession() {
return sf.getCurrentSession();
}
}
三、實際應用
1、編寫測試類(一對多的基本配置和操作)
package com.itzheng.hibernate.demo1;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.itzheng.hibernate.domain.Customer;
import com.itzheng.hibernate.domain.LinkMan;
import com.itzheng.hibernate.utils.HibernateUtils;
/*
* 一對多的測試類
*/
public class HibernateDemo1 {
@Test
// 保存兩個客戶和三個聯繫人 並且建立好關係
public void demo01() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 創建兩個客戶
Customer customer1 = new Customer();
customer1.setCust_name("王東");
Customer customer2 = new Customer();
customer2.setCust_name("趙洪");
// 創建3個聯繫人
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("分解");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("如花");
LinkMan linkMan3 = new LinkMan();
linkMan3.setLkm_name("旺財");
// 設置關係
linkMan1.setCustomer(customer1);// 將客戶放入到聯繫人當中
linkMan2.setCustomer(customer1);
linkMan3.setCustomer(customer2);
customer1.getLinkMans().add(linkMan1);// 將聯繫人放入到客戶的集合當中
customer1.getLinkMans().add(linkMan2);
customer2.getLinkMans().add(linkMan3);
// 保存數據
session.save(linkMan1);
session.save(linkMan2);
session.save(linkMan3);
session.save(customer1);
session.save(customer2);
transaction.commit();
}
}
2、Hibernate的一對多相關的操作
(1)一對多的關係只保存一遍是否可以:
更改核心配置文件
測試代碼
@Test
// 一對多關係只保存一邊 是否可以
public void demo2() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = 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);//只保存客戶
transaction.commit();
}
當只保存客戶時:報錯:瞬時對象異常:持久態對象關聯了瞬時太的對象
customer —>save之後變成持久的了 但是LinkMan 還是瞬時的
當只保存聯繫人的時候:報錯
(2)一對多的級聯操作:
什麼嗎叫做級聯
級聯指的是,我們在操作一個對象的時候,會同時操作其關聯的對象。
級聯是有方向性的
a、操作一的一方的時候,會操作到多的一方。
b、操作多的一方的時候,會操作到一的一方。
3、級聯保存或者更新
(1)保存客戶級聯聯繫人(一的一方配置級聯)
級聯保存或更新的操作
保存客戶級聯聯繫人
操作的主體對象是客戶對象,需要在客戶的映射文件Customer.hbm.xml中 進行配置
cascade配置級聯
@Test
// 級聯保存或更新的操作
//保存客戶級聯聯繫人
//操作的主體對象是客戶對象,需要在客戶的映射文件Customer.hbm.xml中 進行配置
public void demo3() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = 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);//只保存客戶
transaction.commit();
}
(2)保存聯繫人級聯客戶(多的一方配置級聯)
級聯保存或更新的操作
保存聯繫人級聯客戶
操作的主體對象是客戶對象,需要在客戶的映射文件LinkMan.hbm.xml中 進行配置
@Test
// 級聯保存或更新的操作
// 保存聯繫人級聯客戶
// 操作的主體對象是客戶對象,需要在客戶的映射文件LinkMan.hbm.xml中 進行配置
public void demo4() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = 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(linkMan);// 只保聯繫人
transaction.commit();
}
(3)測試對象的導航
測試對象的導航
前提:一對多的雙方都設置了cascade=“save-update”
@Test
// 測試對象的導航
// 前提:一對多的雙方都設置了cascade="save-update"
public void demo5() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("鐵蛋");
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("小花");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("大花");
LinkMan linkMan3 = new LinkMan();
linkMan3.setLkm_name("二花");
linkMan1.setCustomer(customer);// 1號連續人關聯客戶
customer.getLinkMans().add(linkMan2);// 客戶關聯二號聯繫人
customer.getLinkMans().add(linkMan3);// 客戶關聯三號聯繫人
// 雙方都設置了cascade
session.save(linkMan1);// 發送4條insert語句,當保存一號聯繫人的時候,同時會將客戶,二號聯繫人和三號聯繫都保存進去
transaction.commit();
}
發送4條insert語句,當保存一號聯繫人的時候,同時會將客戶,二號聯繫人和三號聯繫都保存進去
如果存儲的是customer對象
發送3條insert語句 存儲 客戶 聯繫人2 聯繫人3
如果存儲的是linkMan2對象
發送一條insert語句
4、級聯刪除
1、級聯刪除:
刪除一邊的時候,同時會刪除另一方的數據也一併刪除。
2、刪除客戶級聯刪除聯繫人
(1)先往數據庫當中保存數據
@Test
// 保存兩個客戶和三個聯繫人 並且建立好關係
public void demo01() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 創建兩個客戶
Customer customer1 = new Customer();
customer1.setCust_name("王東");
Customer customer2 = new Customer();
customer2.setCust_name("趙洪");
// 創建3個聯繫人
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("分解");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("如花");
LinkMan linkMan3 = new LinkMan();
linkMan3.setLkm_name("旺財");
// 設置關係
linkMan1.setCustomer(customer1);// 將客戶放入到聯繫人當中
linkMan2.setCustomer(customer1);
linkMan3.setCustomer(customer2);
customer1.getLinkMans().add(linkMan1);// 將聯繫人放入到客戶的集合當中
customer1.getLinkMans().add(linkMan2);
customer2.getLinkMans().add(linkMan3);
// 保存數據
session.save(linkMan1);
session.save(linkMan2);
session.save(linkMan3);
session.save(customer1);
session.save(customer2);
transaction.commit();
}
在數據庫當中直接刪除客戶是無法刪除的,因爲如果要刪除客戶首先要刪除對應的聯繫人,之後才能刪除客戶(因爲在創建聯繫人的時候設置了外鍵)
3、默認情況下刪除
@Test
//級聯刪除:
//刪除客戶級聯刪除聯繫人
public void demo6() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//沒有設置級聯刪除:默認情況
Customer customer = session.get(Customer.class,1l);//要想刪除線查詢對應的內容
session.delete(customer);
transaction.commit();
}
默認情況下刪除客戶會先將聯繫人表當中的外鍵置空,然後刪除客戶對應表當中的數據
4、自定義情況下級聯刪除刪除客戶同時刪除聯繫人
級聯刪除:
刪除客戶級聯刪除聯繫人,刪除的主體是客戶,需要在客戶的映射文件當中Customer.hbm.xml配置
update保存或者更新
delete級聯刪除
@Test
// 級聯刪除:
// 刪除客戶級聯刪除聯繫人,刪除的主體是客戶,需要在客戶的映射文件當中Customer.hbm.xml配置
//
public void demo6() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 沒有設置級聯刪除:默認情況
// Customer customer = session.get(Customer.class,1l);//要想刪除線查詢對應的內容
// session.delete(customer);
Customer customer = session.get(Customer.class, 1l);// 要想刪除線查詢對應的內容
session.delete(customer);
transaction.commit();
}
5、刪除聯繫人的時候同時級聯刪除客戶(基本不用)
級聯刪除:
刪除聯繫人級聯刪除客戶,刪除的主體是聯繫人,需要在聯繫人的映射文件當中LinkMan.hbm.xml配置
@Test
// 級聯刪除:
// 刪除聯繫人級聯刪除客戶,刪除的主體是聯繫人,需要在聯繫人的映射文件當中LinkMan.hbm.xml配置
public void demo7() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
//刪除聯繫人的同時刪除客戶
LinkMan linkMan = session.get(LinkMan.class, 3l);//查詢對應id的聯繫人對象
session.delete(linkMan);
transaction.commit();
}
5、在一對多當中設置了雙向的關聯,產生一些多餘的SQL語句
(1)舉個栗子
運行demo1,創建兩個客戶
@Test
// 保存兩個客戶和三個聯繫人 並且建立好關係
public void demo01() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 創建兩個客戶
Customer customer1 = new Customer();
customer1.setCust_name("王東");
Customer customer2 = new Customer();
customer2.setCust_name("趙洪");
// 創建3個聯繫人
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkm_name("分解");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkm_name("如花");
LinkMan linkMan3 = new LinkMan();
linkMan3.setLkm_name("旺財");
// 設置關係
linkMan1.setCustomer(customer1);// 將客戶放入到聯繫人當中
linkMan2.setCustomer(customer1);
linkMan3.setCustomer(customer2);
customer1.getLinkMans().add(linkMan1);// 將聯繫人放入到客戶的集合當中
customer1.getLinkMans().add(linkMan2);
customer2.getLinkMans().add(linkMan3);
// 保存數據
session.save(linkMan1);
session.save(linkMan2);
session.save(linkMan3);
session.save(customer1);
session.save(customer2);
transaction.commit();
}
public void demo8() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
// 查詢二號聯繫人
LinkMan linkMan = session.get(LinkMan.class, 2l);
// 查詢2號客戶
Customer customer = session.get(Customer.class, 2l);
// 雙向的關聯
// 將查詢到的客戶設置到2號聯繫人當中
linkMan.setCustomer(customer);// 直接將對象放入到linkMan當中
// 將新設置的2號聯繫人放入到2號客戶當中
customer.getLinkMans().add(linkMan);// 獲取到linkMan集合將對象添加進去
transaction.commit();
}
重複修改lkm_cust_id
更改Linkman和Customer都活修改外鍵
產生多餘的SQL語句
(2)解決產生多餘SQL語句
a、單向維護:
b、使其一方放棄外鍵維護權
使一的一方放棄外鍵維護權,多的一方維護和一的關係
在Customer.hbm.xml上
設置inverse=“true” 默認情況是false,設置爲true爲放棄外鍵使用權
再次執行上述demo8,只發送一次update語句,修改一次外鍵,而且是多的一方在變換時修改的外鍵
c、在一對多關聯查詢修改的時候。
6、區分cascade和inverse
核心配置文件hibernate.cfg.xml
LinkMan.hbm.xml當中
Customer.hbm.xml
測試類
@Test
// 區分cascade和inverse的區別
public void demo9() {
Session session = HibernateUtils.getCurrentSession();
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("李冰");
LinkMan linkMan = new LinkMan();
linkMan.setLkm_name("分解");
customer.getLinkMans().add(linkMan);//獲取到list集合將對象放入到集合當中
//條件是在Customer.hbm.xml的set當中配置了cascade="save-update" inverse="true"
session.save(customer);//客戶會插入到數據庫,聯繫人也會插入到數據庫,但是外鍵爲null,因爲cascade沒有控制外鍵的權利
transaction.commit();
}
客戶會插入到數據庫,聯繫人也會插入到數據庫,但是外鍵爲null,因爲cascade沒有控制外鍵的權利