說明:
1.這裏是使用 客戶-聯繫人 來做這個雙向一對多關聯映射的。即,一個客戶對應多個聯繫人,多個聯繫人對應一個客戶,因此 客戶 和 聯繫人 是一對多 關聯關係。
2.有關SQL語句:只需在hibernate.cfg.xml配置文件中加上以下配置,然後當類獲取Session時,即可讓hibernate通過映射文件來自動生成數據表。
<!-- update表示檢測實體類的映射配置和數據庫的表結構是否一致 -->
<property name="hibernate.hbm2ddl.auto">update</property>
3.本示例的代碼中有詳細的註釋,具體請參考註釋內容。
4.學習多表映射配置要遵循的步驟:
①確定兩張表之間的關係。
②在數據庫中實現兩張表之間的關係建立。
③在實體類中描述出兩個實體之間的關係。
④在映射配置文件中建立兩個實體和兩張表之間的關係。
============================================================
============================================================
下面開始編寫測試代碼:
1.一對多關係映射配置及操作(客戶和聯繫人兩張表)
①確定兩張表之間的關係:
a.一個客戶可以包含多個聯繫人。
b.多個聯繫人可以屬於同一個客戶。
c.客戶和聯繫人之間的關係是一對多。
②在數據庫中實現兩張表之間的關係建立:
a.實現一對多的關係,靠外鍵。
b.客戶表是主表,聯繫人表是從表。
c.我們需要在聯繫人表中添加外鍵。
③在實體類中描述出兩個實體之間的關係:
a.主表的實體類應該包含從表實體類的集合引用。
b.從表的實體類應該包含主表實體類的對象引用。
④在映射配置文件中建立兩個實體和兩張表之間的關係
============================
2.項目結構
============================
首先是pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lin</groupId>
<artifactId>Hibernate_OneToMany_heima</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.10.Final</version>
</dependency>
<!-- MySQL驅動包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>
<!-- jstl、servlet-api、junit -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit-dep</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
============================
4.實體類和其對應的映射文件:
這裏要注意的一點就是,如果要生成類的toString()方法,那麼不要包含關聯的實體類。
Customer.java
package com.lin.domain;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
/**
* 客戶的實體類
* */
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
private Long custId;
private String custName;
private String custSource;
private String sustIndustry;
private String custLevel;
private String custAddress;
private String custPhone;
//一對多關係映射:多的一方
//主表實體應該包含從表實體的集合引用
private Set<LinkMan> linkMans = new HashSet<LinkMan>(0);
public Customer() {
super();
}
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getSustIndustry() {
return sustIndustry;
}
public void setSustIndustry(String sustIndustry) {
this.sustIndustry = sustIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public Set<LinkMan> getLinkMans() {
return linkMans;
}
public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", sustIndustry=" + sustIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
Customer.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.lin.domain">
<!-- name指定持久化類的類名,table指定數據表的表名 -->
<class name="Customer" table="cst_customer">
<id name="custId" column="cust_id">
<generator class="native"/>
</id>
<property name="custName" column="cust_name"></property>
<property name="custSource" column="cust_source"></property>
<property name="sustIndustry" column="sust_industry"></property>
<property name="custLevel" column="cust_level"></property>
<property name="custAddress" column="cust_address"></property>
<property name="custPhone" column="cust_phone"></property>
<!--
一對多關係映射:主表實體的映射配置
涉及的標籤:
set:
作用:用於配置set集合屬性。
屬性: name:指定實體類中set集合的屬性名稱。
table:指定從表的名稱。在一對多配置時可不寫。
inverse:是否放棄維護關聯關係的權利
true:放棄
false:不放棄(默認),如果不放棄,那麼在雙向一對多中保存數據的時候會有多餘的update語句。
cascade:配置級聯操作。
級聯保存更新的取值:save-update
lazy:配置是否使用延遲加載
true:使用延遲加載(默認)
false:不使用延遲加載
key:
作用:用於映射外鍵字段。
屬性:column:指定外鍵字段名稱。
one-to-many:
作用:用於建立一對多的映射配置。
屬性:class:用於指定從表實體的名稱。
-->
<set name="linkMans" table="cst_linkman" inverse="true" cascade="save-update" lazy="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"></one-to-many>
</set>
</class>
</hibernate-mapping>
LinkMan.java
package com.lin.domain;
import java.io.Serializable;
/**
* 聯繫人的實體類
* */
public class LinkMan implements Serializable {
private static final long serialVersionUID = 1L;
private Long lkmId;
private String lkmName;
private String lkmGender;
private String lkmPhone;
private String lkmMobile;
private String lkmEmail;
private String lkmPosition;
private String lkmMemo;
//一對多關係映射,多的一方。
//從表實體包含主表實體的對象引用
private Customer customer;
public Long getLkmId() {
return lkmId;
}
public void setLkmId(Long lkmId) {
this.lkmId = lkmId;
}
public String getLkmName() {
return lkmName;
}
public void setLkmName(String lkmName) {
this.lkmName = lkmName;
}
public String getLkmGender() {
return lkmGender;
}
public void setLkmGender(String lkmGender) {
this.lkmGender = lkmGender;
}
public String getLkmPhone() {
return lkmPhone;
}
public void setLkmPhone(String lkmPhone) {
this.lkmPhone = lkmPhone;
}
public String getLkmMobile() {
return lkmMobile;
}
public void setLkmMobile(String lkmMobile) {
this.lkmMobile = lkmMobile;
}
public String getLkmEmail() {
return lkmEmail;
}
public void setLkmEmail(String lkmEmail) {
this.lkmEmail = lkmEmail;
}
public String getLkmPosition() {
return lkmPosition;
}
public void setLkmPosition(String lkmPosition) {
this.lkmPosition = lkmPosition;
}
public String getLkmMemo() {
return lkmMemo;
}
public void setLkmMemo(String lkmMemo) {
this.lkmMemo = lkmMemo;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "LinkMan [lkmId=" + lkmId + ", lkmName=" + lkmName + ", lkmGender=" + lkmGender + ", lkmPhone="
+ lkmPhone + ", lkmMobile=" + lkmMobile + ", lkmEmail=" + lkmEmail + ", lkmPosition=" + lkmPosition
+ ", lkmMemo=" + lkmMemo + "]";
}
}
LinkMan.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.lin.domain">
<!-- name指定持久化類的類名,table指定數據表的表名 -->
<class name="LinkMan" table="cst_linkman">
<id name="lkmId" column="lkm_id">
<generator class="native"/>
</id>
<property name="lkmName" column="lkm_name"></property>
<property name="lkmGender" column="lkm_gender"></property>
<property name="lkmPhone" column="lkm_phone"></property>
<property name="lkmMobile" column="lkm_mobile"></property>
<property name="lkmEmail" column="lkm_email"></property>
<property name="lkmPosition" column="lkm_position"></property>
<property name="lkmMemo" column="lkm_memo"></property>
<!--
一對多關係映射:從表實體的映射配置
涉及的標籤:many-to-one.
作用:建立多對一的映射配置
屬性: name:從表實體中引用主表實體對象引用的名稱
class:指定屬性所對應的實體類名稱
column:指定從表中外鍵字段的名稱
lazy:配置是否使用延遲加載
false:使用立即加載
proxy:是看load方法是延遲加載還是立即加載
no-proxy:不使用,不用管。
-->
<many-to-one name="customer" class="Customer" column="lkm_cust_id" cascade="save-update" lazy="proxy"></many-to-one>
</class>
</hibernate-mapping>
============================
5.Hibernate的配置文件:
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<!--
Hibernate配置文件包含了連接持久層與映射文件所需的基本信息。
(Hibernate配置文件主要用來配置數據庫連接以及Hibernate運行時所需的各個屬性的值。)
-->
<hibernate-configuration>
<session-factory>
<!-- 數據庫連接設置 -->
<!-- 配置數據庫JDBC驅動 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<!-- 配置數據庫連接URL -->
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate</property>
<!-- 配置數據庫用戶名 -->
<property name="hibernate.connection.username">root</property>
<!-- 配置數據庫密碼 -->
<property name="hibernate.connection.password">000000</property>
<!-- 配置JDBC內置連接池 -->
<property name="connection.pool_size">1</property>
<!-- 配置數據庫方言 -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 配置Hibernate採用何種方式生成DDL語句 -->
<!-- update表示檢測實體類的映射配置和數據庫的表結構是否一致 -->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 輸出運行時生成的SQL語句 -->
<property name="show_sql">true</property>
<!-- 列出所有的映射文件 -->
<mapping resource="hibernate/mappings/LinkMan.hbm.xml" />
<mapping resource="hibernate/mappings/Customer.hbm.xml" />
</session-factory>
</hibernate-configuration>
============================
6.Hibernate的工具類:
在這個類中添加了main方法,運行main方法後hibernate即可根據映射文件自動生成表。
HibernateUtil.java
package com.lin.utils;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static SessionFactory sessionFactory;
private static Configuration configuration;
//創建線程局部變量threadLocal,用來保存Hibernate的Session
private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
//使用靜態代碼塊初始化Hibernate
static{
try{
//如果不指定hibernate的配置文件位置,那麼它會默認到classpath路徑下查找名爲hibernate.cfg.xml的文件
Configuration cfg = new Configuration().configure("/hibernate/hibernate.cfg.xml");
//創建SessionFactory
sessionFactory = cfg.buildSessionFactory();
}catch(Throwable ex){
throw new ExceptionInInitializerError(ex);
}
}
//獲得SessionFactory
public static SessionFactory getSessionFactory(){
return sessionFactory;
}
//獲得ThreadLocal對象管理的Session實例
public static Session getSession() throws HibernateException {
Session session = (Session)threadLocal.get();
if(session == null || session.isOpen()){
if(sessionFactory == null){
rebuildSessionFactory();
}
//通過SessionFactory對象創建Session對象
session = (sessionFactory != null)?sessionFactory.openSession():null;
//將新打開的Session實例保存到線程局部變量threadLocal中
threadLocal.set(session);
}
return session;
}
//關閉Session實例
public static void closeSession() throws HibernateException {
//從線程局部變量threadLocal中獲取之前存入的Session實例
Session session = (Session)threadLocal.get();
threadLocal.set(null);
if(session != null){
session.close();
}
}
//重建SessionFactory
public static void rebuildSessionFactory() {
try{
configuration.configure("/hibernate/hibernate.cfg.xml");
sessionFactory = configuration.buildSessionFactory();
}catch(Exception e){
System.out.println("Error Creating SessionFactory ");
e.printStackTrace();
}
}
//關閉緩存和連接池
public static void shutdown(){
getSessionFactory().close();
}
//main方法
public static void main(String[] args){
HibernateUtil.getSession();
}
}
============================
7.Hibernate一對多關聯關係的CRUD測試:
package com.lin.test;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.lin.domain.Customer;
import com.lin.domain.LinkMan;
import com.lin.utils.HibernateUtil;
/**
* 一對多關係的CRUD操作
* */
public class HibernateOneToManyTest {
/**
* 保存操作。
* 正常的保存:創建一個新的聯繫人(數據庫中沒有),需要關聯一個客戶(數據庫中有)
* */
@Test
public void saveTest1(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//1.查詢一個客戶
Customer c1 = (Customer) session.get(Customer.class, 1L);
//2.創建一個新的聯繫人
LinkMan l = new LinkMan();
l.setLkmName("聯繫人1");
//3.建立客戶和聯繫人的關聯關係(讓聯繫人知道屬於哪個客戶即可)
l.setCustomer(c1);
//4.保存聯繫人
session.save(l);
tx.commit();
session.close();
}
/**
* 特殊的情況:
* 創建一個客戶(數據庫中沒有)和一個聯繫人(數據庫中沒有)。
* 建立聯繫人和客戶的雙向關聯關係。
* 使用符合原則的保存。(原則是:先保存主表實體,再保存從表實體)。
*
*
* 此時保存會有問題,
* 我們保存兩個實體,應該只有兩條insert語句,而執行結果卻多了一條update語句。
* 解決辦法:
* 讓客戶在執行保存的時候,放棄維護關聯關係的權利。
* 配置的方式:
* 在Customer的映射配置文件中的set標籤上使用inverse屬性。
* inverse的含義:是否放棄維護關聯關係的權利
* true:放棄
* false:不放棄(默認)
* */
@Test
public void saveTest2(){
//1.創建一個客戶
Customer c1 = new Customer(); //瞬時態
c1.setCustName("客戶2");
//2.創建一個新的聯繫人
LinkMan l = new LinkMan(); //瞬時態
l.setLkmName("聯繫人2");
//3.建立客戶和聯繫人的關聯關係(雙向)
l.setCustomer(c1);
c1.getLinkMans().add(l);
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//4.保存,要符合原則
session.save(c1); //持久態,有一級緩存和快照
session.save(l); //持久態,有一級緩存和快照
tx.commit();
session.close();
}
/**
* 保存操作:級聯保存
*
* 使用級聯保存,
* 1.也可以配置在many-to-one上。
* 2.配置的方式是找到Customer的映射配置文件中的Set標籤,在上面加入cascade屬性:
* cascade:配置級聯操作。
* 級聯保存更新的取值:save-update
*/
@Test
public void saveTest3(){
//1.創建一個客戶
Customer c1 = new Customer(); //瞬時態
c1.setCustName("客戶3");
//2.創建一個新的聯繫人
LinkMan l = new LinkMan(); //瞬時態
l.setLkmName("聯繫人3");
//3.建立客戶和聯繫人的關聯關係(雙向)
l.setCustomer(c1);
c1.getLinkMans().add(l);
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//4.保存,要符合原則
session.save(c1); //持久態,有一級緩存和快照
tx.commit();
session.close();
}
/**
* 級聯保存2
* */
@Test
public void saveTest4(){
//1.創建一個客戶
Customer c1 = new Customer(); //瞬時態
c1.setCustName("客戶4");
//2.創建一個新的聯繫人
LinkMan l = new LinkMan(); //瞬時態
l.setLkmName("聯繫人4");
//3.建立客戶和聯繫人的關聯關係(雙向)
l.setCustomer(c1);
c1.getLinkMans().add(l);
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//4.保存,要符合原則
session.save(l); //持久態,有一級緩存和快照
tx.commit();
session.close();
}
/**
* 更新操作
*
* 要求:
* 創建一個新的聯繫人,查詢一個已有客戶。
* 建立新聯繫人和已有客戶的雙向關聯關係
* 更新客戶
* */
@Test
public void updateTest1(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//1.查詢一個客戶
Customer c1 = (Customer) session.get(Customer.class, 1L);
//2.創建一個新的聯繫人
LinkMan l = new LinkMan();
l.setLkmName("聯繫人5");
//3.建立客戶和聯繫人的關聯關係(雙向)
l.setCustomer(c1);
c1.getLinkMans().add(l);
//4.更新客戶
session.update(c1);
tx.commit();
session.close();
}
/**
* 刪除操作
* 1.刪除從表數據就是單表操作。
* 2.刪除主表數據:
* 看有沒有從表數據引用
* 沒有引用:就是單表操作,直接刪。
* 有引用:
* 在刪除時,hibernate會把從表中的外鍵字段置爲null,然後再刪除主表數據。(注意:主表中要不能放棄維護從表數據,即inverse="false" )
* 如果外鍵字段有非空約束,則hibernate不能更新外鍵字段爲null,會報錯。
* 此時,如果仍然想刪除,就需要使用級聯刪除。可將cascade="save-update,delete",同時必須配置inverse="true"
*
* 級聯刪除的使用: 在實際開發中,要慎重!
*
* */
@Test
public void deleteTest(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
//1.查詢一個客戶
Customer c1 = (Customer) session.get(Customer.class, 3L);
//2.刪除id爲1的客戶
session.delete(c1);
tx.commit();
session.close();
}
/****************************************************
* Hibernate 中的查詢操作:OID查詢,HQL查詢,QBC查詢,SQL查詢,對象導航查詢。
*
*
* 以下通過一對多的來演示對象導航查詢。
* 當兩個實體之間有關聯關係時(關聯關係可以使一對一,一對多(多對一),多對多中的任意一種)。
* 我們通過調用getXxx方法即可實現查詢功能(功能是由hibernate提供的)
* 例如:
* customer.getLinkMans()就可以得到當前客戶下的所有聯繫人
* linkman.getCustomer()就可以得到當前聯繫人下的所屬客戶。
*
*
*三種lazy屬性的情況:
* class標籤的lazy屬性:它只能管load方法是否是延遲加載。
* set標籤的lazy屬性: 它管查詢關聯的集合對象是否是延遲加載。
* many-to-one的lazy屬性:它管查詢關聯的主表實體是否是立即加載。
*
***************************************************/
/**
* 查詢id爲1的客戶下所有的聯繫人
* 一對多時,根據一的一方查詢多的一方時,需要使用延遲加載。(默認配置就是使用延遲加載)
* (如果這裏需要改成立即加載,那麼找到Customer的映射文件中的set標籤,添加lazy屬性,即 lazy="false"即可)
* */
@Test
public void selectTest1(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
Customer c = (Customer) session.get(Customer.class, 1L);
System.out.println(c);
System.out.println(c.getLinkMans());
tx.commit();
session.close();
}
/**
* 查詢id爲1的聯繫人所屬的客戶
* 多對一時,根據多的一方查詢一的一方時,不需要使用延遲加載,而是使用立即加載,這裏需要配置一下。(找到LinkMan的配置文件的many-to-one標籤添加laze屬性,即lazy="false"即可)
* 需要找到聯繫人的映射配置文件:在many-to-one標籤上使用lazy屬性
* 取值有:
* false:使用立即加載。
* proxy:是看load方法是延遲加載還是立即加載。
* no-proxy:不使用,不用管。
* */
@Test
public void selectTest2(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
LinkMan l = (LinkMan) session.get(LinkMan.class, 1L);
System.out.println(l);
System.out.println(l.getCustomer());
tx.commit();
session.close();
}
/**
* 關於load方法改爲立即加載的方式。
* 找到查詢實體的映射配置文件,它的class標籤上也有一個lazy屬性。
* 含義是:是否延遲加載
* true:延遲加載(默認值)
* flase:立即加載
* */
@Test
public void selectTest3(){
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
Customer c = (Customer) session.load(Customer.class,1L);
System.out.println(c);
tx.commit();
session.close();
}
}
==========================
至此,一對多關聯關係的全部代碼都在上面。測試效果就免了~