1. 程式人生 > >Hibernate-03 關聯映射

Hibernate-03 關聯映射

chang dna his 問題: ati 開啟事務 聲明 會話 cti

學習任務

  • 關聯映射
  • inverse屬性、cascade屬性
  • 單向的多對一、雙向的一對多映射
  • 多對多映射


關聯關系

類與類之間最普遍的關系就是關聯關系。

單向的關聯

技術分享圖片

雙向的關聯

技術分享圖片

單向多對一關聯

以Emp和Dept為例。

配置單向多對一關聯

Emp類中需要添加Dept屬性。

1.Dept類

package com.etc.entity;

import java.io.Serializable;

/** 部門持久化類 */
public class Dept implements Serializable {

	private static final long serialVersionUID = 1L;

	private Short deptNo;// 部門編號(id屬性。在hibernate中,id屬性被稱為對象標識符---Object Identifier,OID)
	private String deptName;// 部門名稱
	private String location;// 部門所在地區

	public Dept() {
	}
		
	public Dept(Short deptNo, String deptName) {
		super();
		this.deptNo = deptNo;
		this.deptName = deptName;
	}

	public  Short getDeptNo() {
		return deptNo;
	}

	public void setDeptNo( Short deptNo) {
		this.deptNo = deptNo;
	}

	public String getDeptName() {
		return deptName;
	}

	public void setDeptName(String deptName) {
		this.deptName = deptName;
	}

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}

}

  

2.Emp類

package com.etc.entity;

import java.util.Date;

/**員工持久化類*/
public class Emp {
	private Integer empNo;// 員工編號
	private String empName;// 員工姓名
	private String job;// 工作
	private Integer mgr;// 所屬經理
	private Date hireDate;// 入職日期
	private Double sal;// 工資
	private Double comm;// 獎金
	//private Integer deptNo;// 部門編號
	private Dept dept;//部門
public Integer getEmpNo() { return empNo; } public void setEmpNo(Integer empNo) { this.empNo = empNo; } public String getEmpName() { return empName; } public void setEmpName(String empName) { this.empName = empName; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public Integer getMgr() { return mgr; } public void setMgr(Integer mgr) { this.mgr = mgr; } public Date getHireDate() { return hireDate; } public void setHireDate(Date hireDate) { this.hireDate = hireDate; } public Double getSal() { return sal; } public void setSal(Double sal) { this.sal = sal; } public Double getComm() { return comm; } public void setComm(Double comm) { this.comm = comm; } public Dept getDept() { return dept; } public void setDept(Dept dept) { this.dept = dept; } }

  

3.Dept.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.etc.entity.Dept" table="`DEPT`" schema="scott"
		dynamic-update="true">
		<id name="deptNo" type="java.lang.Short" column="`DEPTNO`">
			<!-- 主鍵生成器替換為increment,hibernate負責增長主鍵 -->
			<!-- <generator class="assigned" /> -->
			<generator class="increment"></generator>
		</id>
		<property name="deptName" type="java.lang.String" column="`DNAME`" />
		<property name="location" type="java.lang.String">
			<column name="`LOC`"></column>
		</property>
	</class>
</hibernate-mapping>

  

4.Emp.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.etc.entity.Emp" table="`EMP`" schema="scott"
		dynamic-update="true">
		<id name="empNo" type="java.lang.Integer" column="`EMPNO`">
			<generator class="increment" />
		</id>
		<property name="empName" type="java.lang.String" column="`ENAME`" />
		<property name="job" type="java.lang.String" column="`JOB`" />
		<property name="mgr" type="java.lang.Integer" column="`MGR`" />
		<property name="hireDate" type="java.util.Date" column="`HIREDATE`" />
		<property name="sal" type="java.lang.Double" column="`SAL`" />
		<property name="comm" type="java.lang.Double" column="`COMM`" />
		<!-- 員工和部門之間多多對一關系 -->
		<many-to-one name="dept" column="`DEPTNO`" class="com.etc.entity.Dept" />
	</class>
</hibernate-mapping>

  

<many-to-one>元素建立了Emp表的外健DEPTNO和dept屬性之間的映射。包括以下屬性:

  • name:持久化類的屬性名。
  • column:持久化類屬性對應的表的外鍵。
  • class:持久化類屬性的類型。

實現單向多對一關聯的持久化操作

添加或者修改Emp對象,外鍵信息存儲在Dept對象中

1.DAO關鍵代碼

	/** 保存員工信息 */
	public void save(Emp emp) throws Exception {
		this.getSession().save(emp);
	}

  

2.BIZ關鍵代碼

     /** 添加員工信息 */
	public void addNewEmp(Emp emp) {
		Transaction tx = null;
		try {
			tx = empDao.getSession().beginTransaction();// 開啟事務
			empDao.save(emp);// 持久化操作
			tx.commit(); // 提交事務
		} catch (Exception e) {
			e.printStackTrace();
			if (tx != null) {
				tx.rollback();// 回滾事務
			}
		}
	}

  

3.測試方法關鍵代碼

	/** 測試員工 */
	public void testAdd() {
		// 創建Emp對象
		Emp emp = new Emp();
		emp.setEmpName("張三");
		// 指定員工所在部門
		Dept dept = new Dept();
		dept.setDeptNo((short) 10);
		emp.setDept(dept);
		// 保存員工數據
		new EmpBiz().addNewEmp(emp);
	}

  

4.如果部門存在,生成SQL

Hibernate: 
    insert 
    into
        scott.
        "EMP" ("ENAME", "JOB", "MGR", "HIREDATE", "SAL", "COMM", "DEPTNO", "EMPNO") 
    values
        (?, ?, ?, ?, ?, ?, ?, ?)

  

5.如果部門不存在,將提示錯誤

WARN - SQL Error: 2291, SQLState: 23000
ERROR - ORA-02291: 違反完整約束條件 (SCOTT.FK_DEPTNO) - 未找到父項關鍵字

  

按照指定Dept對象查詢相關Emp對象

1.DAO關鍵代碼

	/** 按照指定的dept對象查詢 */
	public List<Emp> listEmps(Dept dept){
		String hql="from Emp where dept=?";
		return this.getSession().createQuery(hql).setParameter(0, dept).list();
	}

  

2.BIZ關鍵代碼

	/** 按照指定的dept對象查詢 */
	public List<Emp> listEmps(Dept dept) {
		Transaction tx = null;
		List<Emp> list = null;
		try {
			tx = empDao.getSession().beginTransaction();
			list = empDao.listEmps(dept);
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			if (tx != null) {
				tx.rollback();
			}
		}
		return list;
	}

  

3.測試方法關鍵代碼

      /** 測試查詢員工 */	
	public void testList() {
		Dept dept = new Dept();
		dept.setDeptNo((short) 10);
		List<Emp> list = new EmpBiz().listEmps(dept);
		for (Emp item : list) {
			System.out.println(item.getEmpNo() + "\t" + item.getEmpName());
		}

	}

  

輸出指定Emp集合中的所有Emp對象及其關聯的Dept對象信息

由於Emp和Demp之間存在單向多對一關系,所以只要調用emp.getDept()方法,就可以方便地從Emp對象導航到Dept對象。

1.DAO關鍵代碼

	/** 從Emp導航到Dept,輸出部門信息*/
	public List<Emp> listAll(){
		String hql="from Emp";
		return this.getSession().createQuery(hql).list();
	}

  

2.BIZ關鍵代碼

      /** 從Emp導航到Dept,輸出部門信息 */
	public List<Emp> listAll() {
		Transaction tx = null;
		List<Emp> list = null;
		try {
			tx = empDao.getSession().beginTransaction();
			list = empDao.listAll();
			for(Emp item:list){
				System.out.println(item.getEmpNo()+"\t"+item.getEmpName()+"\t"+item.getDept().getDeptName());//會話關閉,無法測試,在biz中測試
			}
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			if (tx != null) {
				tx.rollback();
			}
		}
		return list;
	}

  

3.測試方法關鍵代碼

/** 測試查詢員工:從Emp導航到Dept */
public void testListAll() {
	new EmpBiz().listAll();
}

  

建立雙向一對多關聯關系

雙向關聯關系可以很容易從一個對象導航到另外一個對象或者對象的集合。

配置雙向一對多關聯

在本章單向多對一案例基礎上,Dept對象增加員工集合屬性

1.Dept類的配置

package com.etc.entity;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/** 部門持久化類 */
public class Dept implements Serializable {

	private static final long serialVersionUID = 1L;

	private Short deptNo;// 部門編號(id屬性。在hibernate中,id屬性被稱為對象標識符---Object Identifier,OID)
	private String deptName;// 部門名稱
	private String location;// 部門所在地區
	private Set<Emp> emps=new HashSet<Emp>();//員工集合:一對多雙向關聯
	
	
	public Dept() {
	}
		
	public Dept(Short deptNo, String deptName) {
		super();
		this.deptNo = deptNo;
		this.deptName = deptName;
	}

	public  Short getDeptNo() {
		return deptNo;
	}

	public void setDeptNo( Short deptNo) {
		this.deptNo = deptNo;
	}

	public String getDeptName() {
		return deptName;
	}

	public void setDeptName(String deptName) {
		this.deptName = deptName;
	}

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		this.location = location;
	}

	public Set<Emp> getEmps() {
		return emps;
	}

	public void setEmps(Set<Emp> emps) {
		this.emps = emps;
	}

}

  

註意:Hibernate要求在持久化類中定義集合類型屬性時,必須把屬性聲明為接口類型,如Set或者其泛型Set<Emp>。

同時為了避免訪問集合出現Null異常,在定義集合屬性的使用同時進行實例化。

2.Dept.hbm.xml映射文件的配置

由於在DEPT表中沒有直接與emps屬性對應的字段,所以不能用<property>元素來映射emps屬性,而是使用<set>元素。

<?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.etc.entity.Dept" table="`DEPT`" schema="scott"
		dynamic-update="true">
		<id name="deptNo" type="java.lang.Short" column="`DEPTNO`">
			<!-- 主鍵生成器替換為increment,hibernate負責增長主鍵 -->
			<!-- <generator class="assigned" /> -->
			<generator class="increment"></generator>
		</id>
		<property name="deptName" type="java.lang.String" column="`DNAME`" />
		<property name="location" type="java.lang.String">
			<column name="`LOC`"></column>
		</property>
		<set name="emps">
			<!-- set關聯持久化類所在表的外鍵 -->
			<key column="DEPTNO" />
			<!-- set管理的持久化類 -->
			<one-to-many class="com.etc.entity.Emp" />
		</set>
	</class>
</hibernate-mapping>

  

<set>元素的name屬性:設定持久化類的屬性名,此處為Dept類的emps屬性。<set>元素還包含兩個子元素:

  • <key>元素:column屬性設定與所關聯的持久化類相對應的表的外鍵,此處為EMP表的DEPTNO字段。
  • <one-to-many>元素:class屬性設定所關聯的持久化類型,此處為Emp類。

雙向一對多關聯關系的增刪改操作

在雙向關聯關系中,可以實現對象間的自動化級聯處理。

級聯關系通過持久化類映射文件中的cascade屬性和inverse屬性進行控制。


一、cascade屬性

在Dept、Emp類和相關映射文件已經編寫完成的情況下,需要完成以下工作:

  1. 先創建Dept對象,然後創建一個Emp對象,將兩個對象進行關聯。最後保存Dept對象,同時自動保存Emp對象。
  2. 刪除Dept對象,並級聯刪除與Dept對象關聯的Emp對象。

要完成以上兩個持久化操作, 需要在<set>元素中配置<cascade>屬性。

在對象一關系映射文件中,用於映射持久化類之間關聯關系的元素,如<set>、<many-to-one>都有一個cascade屬性。它用於指定如何操縱與當前對象關聯的其他對象。

cascade屬性值

描 述

none

當Session操縱當前對象時,忽略其他關聯的對象。它是cascade屬性的默認值

save-update

當通過Session的save()、update()及saveOrUpdate()方法來保存或更新當前對象時,級聯保存所有關聯的新建的瞬時狀態的對象,並且級聯更新所有關聯的遊離狀態的對象

merge

當通過Session的merge()方法來保存或更新當前對象時,對其關聯對象也執行merge()方法

delete

當通過Session的delete()方法刪除當前對象時,會級聯刪除所有關聯的對象

all

包含所有的級聯行為

完成第一個持久化工作:添加部門的同時添加員工

1.修改Dept.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.etc.entity.Dept" table="`DEPT`" schema="scott"
		dynamic-update="true">
		<id name="deptNo" type="java.lang.Short" column="`DEPTNO`">
			<!-- 主鍵生成器替換為increment,hibernate負責增長主鍵 -->
			<!-- <generator class="assigned" /> -->
			<generator class="increment"></generator>
		</id>
		<property name="deptName" type="java.lang.String" column="`DNAME`" />
		<property name="location" type="java.lang.String">
			<column name="`LOC`"></column>
		</property>
		<!-- 級聯更新或者保存 -->
		<set name="emps" cascade="save-update">
			<!-- set關聯持久化類所在表的外鍵 -->
			<key column="DEPTNO" />
			<!-- set管理的持久化類 -->
			<one-to-many class="com.etc.entity.Emp" />
		</set>
	</class>
</hibernate-mapping>

  

2.DAO關鍵代碼

	//保存Dept時級聯保存Emp
	public void save(Dept dept) throws Exception{
		this.getSession().save(dept);
	}

  

3.BIZ關鍵代碼

	// 保存Dept時級聯保存Emp
	public void save(Dept dept) {
		Transaction tx=null;
		try{
			tx=deptDao.getSession().beginTransaction();
			deptDao.save(dept);
			tx.commit();			
		}catch (Exception e) {
			e.printStackTrace();
			if(tx!=null){
				tx.rollback();
			}
		}
	}

  

4.測試方法代碼

	// 保存Dept時級聯保存Emp	
	public void testAdd() {
		Dept dept = new Dept();
		//dept.setDeptNo((short) 80);
		dept.setDeptName("開發部");
		dept.setLocation("廈門");
		
		Emp emp=new Emp();
		emp.setEmpName("李四");
		
		dept.getEmps().add(emp);
		//保存部門信息
		new DeptBiz().save(dept);
	}

  

5.生成相關SQL語句

Hibernate: 
    select
        max("DEPTNO") 
    from
        "DEPT"
Hibernate: 
    select
        max("EMPNO") 
    from
        "EMP"
Hibernate: 
    insert 
    into
        scott.
        "DEPT" ("DNAME", "LOC", "DEPTNO") 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        scott.
        "EMP" ("ENAME", "JOB", "MGR", "HIREDATE", "SAL", "COMM", "DEPTNO", "EMPNO") 
    values
        (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    update
        scott."EMP" 
    set
        DEPTNO=? 
    where
        "EMPNO"=?

  

完成第二個持久化工作:刪除部門對象的同時刪除關聯員工對象

1.修改Dept.hbm.xml映射文件

		<!-- 級聯刪除 -->
		<set name="emps" cascade="delete">
			<!-- set關聯持久化類所在表的外鍵 -->
			<key column="DEPTNO" />
			<!-- set管理的持久化類 -->
			<one-to-many class="com.etc.entity.Emp" />
		</set>

  

2.DAO關鍵代碼

     //刪除Dept時級聯刪除Emp	
	//刪除前要根據對象id從數據庫加載對象
	public Dept load(Serializable id){
		return (Dept) this.getSession().load(Dept.class, id);
	}
	public void delete(Dept dept) throws Exception{
		this.getSession().delete(this.load(dept.getDeptNo()));
	}

  

3.BIZ關鍵代碼

        // 刪除Dept時級聯刪除Emp
	public void delete(Dept dept) {
		Transaction tx=null;
		try{
			tx=deptDao.getSession().beginTransaction();
			deptDao.delete(dept);
			tx.commit();			
		}catch (Exception e) {
			e.printStackTrace();
			if(tx!=null){
				tx.rollback();
			}
		}
	}    

  

4.測試方法關鍵代碼

	// 刪除Dept時級聯刪除Emp	
	public void testDel() {
		Dept dept = new Dept();
		dept.setDeptNo((short)42);
		new DeptBiz().delete(dept);
	}

  

二.<set>元素的inverse屬性

<set>元素的inverse屬性的值有兩個,即true和false,默認為false。

  • inverse設置為false,則為主動方,由主動方負責維護關聯關系,默認是false。
  • inverse設置為true,不負責維護關聯關系。

員工調整部門示例。

1.Emp.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.etc.entity.Emp" table="`EMP`" schema="scott"
		dynamic-update="true">
		<id name="empNo" type="java.lang.Integer" column="`EMPNO`">
			<generator class="increment" />
		</id>
		<property name="empName" type="java.lang.String" column="`ENAME`" />
		<property name="job" type="java.lang.String" column="`JOB`" />
		<property name="mgr" type="java.lang.Integer" column="`MGR`" />
		<property name="hireDate" type="java.util.Date" column="`HIREDATE`" />
		<property name="sal" type="java.lang.Double" column="`SAL`" />
		<property name="comm" type="java.lang.Double" column="`COMM`" />
		<!-- 員工和部門之間多多對一關系 -->
		<many-to-one name="dept" column="`DEPTNO`" class="com.etc.entity.Dept"  />
	</class>
</hibernate-mapping>

  

2.Dept.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.etc.entity.Dept" table="`DEPT`" schema="scott"
		dynamic-update="true">
		<id name="deptNo" type="java.lang.Short" column="`DEPTNO`">
			<!-- 主鍵生成器替換為increment,hibernate負責增長主鍵 -->
			<!-- <generator class="assigned" /> -->
			<generator class="increment"></generator>
		</id>
		<property name="deptName" type="java.lang.String" column="`DNAME`" />
		<property name="location" type="java.lang.String">
			<column name="`LOC`"></column>
		</property>
		<set name="emps">
			<!-- set關聯持久化類所在表的外鍵 -->
			<key column="DEPTNO" />
			<!-- set管理的持久化類 -->
			<one-to-many class="com.etc.entity.Emp" />
		</set>
	</class>
</hibernate-mapping>

  

3.DAO關鍵代碼

/**DeptDAO:加載部門對象*/
public Dept load(Serializable id){
	return (Dept) this.getSession().load(Dept.class, id);
}

/**EmpDAO:加載員工對象*/
public Emp load(Serializable empNo){
	return (Emp) this.getSession().load(Emp.class, empNo);		
}

  

4.BIZ關鍵代碼

	/** 調整部門 */
	public void changeDept(Integer empNo, Short deptNo) {
		Transaction tx = null;
		try {
			tx = empDao.getSession().beginTransaction();
			// 加載Emp和Dept持久化對象
			Dept dept = new DeptDao().load(deptNo);
			Emp emp = empDao.load(empNo);
			// 建立dept和emp對象的關聯關系
			emp.setDept(dept);
			dept.getEmps().add(emp);
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			if (tx != null) {
				tx.rollback();
			}
		}
	}

  

5.測試方法關鍵代碼

	/**測試部門調整*/
	@Test
	public void testInverse(){
		new EmpBiz().changeDept(7369, (short)40);//原來部門編號為20
	}

  

6.SQL語句分析

# Hibernate:加載員工對象
    select
        emp0_."EMPNO" as EMPNO1_1_0_,
        emp0_."ENAME" as ENAME2_1_0_,
        emp0_."JOB" as JOB3_1_0_,
        emp0_."MGR" as MGR4_1_0_,
        emp0_."HIREDATE" as HIREDATE5_1_0_,
        emp0_."SAL" as SAL6_1_0_,
        emp0_."COMM" as COMM7_1_0_,
        emp0_."DEPTNO" as DEPTNO8_1_0_ 
    from
        scott."EMP" emp0_ 
    where
        emp0_."EMPNO"=?

# Hibernate:加載部門對象
    select
        dept0_."DEPTNO" as DEPTNO1_0_0_,
        dept0_."DNAME" as DNAME2_0_0_,
        dept0_."LOC" as LOC3_0_0_ 
    from
        scott."DEPT" dept0_ 
    where
        dept0_."DEPTNO"=?

# Hibernate:根據部門編號查找員工對象集合 (對應:dept.getEmps().add(emp);語句中的getEmps()生成的SQL)
    select
        emps0_.DEPTNO as DEPTNO0_1_,
        emps0_."EMPNO" as EMPNO1_1_,
        emps0_."EMPNO" as EMPNO1_1_0_,
        emps0_."ENAME" as ENAME2_1_0_,
        emps0_."JOB" as JOB3_1_0_,
        emps0_."MGR" as MGR4_1_0_,
        emps0_."HIREDATE" as HIREDATE5_1_0_,
        emps0_."SAL" as SAL6_1_0_,
        emps0_."COMM" as COMM7_1_0_,
        emps0_."DEPTNO" as DEPTNO8_1_0_ 
    from
        scott."EMP" emps0_ 
    where
        emps0_.DEPTNO=?

# Hibernate:更新員工的部門編號信息(emp.setDept(dept);語句中emp對象屬性發生變化,Hibernate刷新緩存生成的SQL)
    update
        scott."EMP" 
    set
        "DEPTNO"=? 
    where
        "EMPNO"=?

# Hibernate:更新員工的部門編號信息(dept主動維護數據導致:dept.getEmps().add(emp);語句導致,Hibernate檢查到dept對象的emps屬性發生變化後,會去維護emp對象的deptno,確保數據庫外鍵的正確)
    update
        scott."EMP" 
    set
        "DEPTNO"=? 
    where
        "EMPNO"=?

  

問題:hibernate重復執行了update語句,影響了效率。實際上emp.setDept(dept)語句已經正確設定了emp和dept對象的關聯關系(dept對象為加載出來持久態對象)。

解決:通過設定<set>元素的inverse屬性值為true來解決該問題。

Dept.hbm.xml關鍵代碼:

		<set name="emps" inverse="true">
			<!-- set關聯持久化類所在表的外鍵 -->
			<key column="DEPTNO" />
			<!-- set管理的持久化類 -->
			<one-to-many class="com.etc.entity.Emp" />
		</set>

  

控制臺將只輸出一條UPDATE SQL語句。

<set>標簽的inverse設置為true後可能導致的問題:

1.僅建立Emp對Dept的關聯關系,可以正確更新Emp數據。關鍵代碼如下:

			tx = empDao.getSession().beginTransaction();
			// 加載Emp和Dept持久化對象
			Dept dept = new DeptDao().load(deptNo);
			Emp emp = empDao.load(empNo);
			// 僅建立emp對象的關聯關系
			emp.setDept(dept);
			tx.commit();

  

2.如果不建立Emp到Dept對象的關聯,導致Emp對象的數據庫表中的外鍵無法得到更新,關鍵代碼如下:

			tx = empDao.getSession().beginTransaction();
			// 加載Emp和Dept持久化對象
			Dept dept = new DeptDao().load(deptNo);
			Emp emp = empDao.load(empNo);
			// 僅建立dept對emp對象的關聯關系
			dept.getEmps().add(emp);
			tx.commit();

  

3.inverse屬性使用建議

  • 建議關聯對象“一”方把inverse設置為true,提高應用性能。
  • 在建立兩個對象的雙向關聯時,應該同時修改兩個關聯對象的相關屬性。
  • 解除雙向關聯時,也要同時修改關聯兩端的對象的相應屬性。代碼如下所示:
			emp.setDept(null);
			dept.getEmps().remove(emp);
  • set元素屬性的參考配置
<!-- 級聯刪除、保存和更新,放棄主動維護對象關聯 -->
<set name="emps" cascade="all" inverse="true">

  

建立多對多關聯關系

多對多數據表SQL語句

SELECT * FROM emp;

DROP TABLE PROEMP;
DROP TABLE PROJECT;
DROP TABLE EMPLOYEE;

# 項目表
CREATE TABLE PROJECT(
PROID NUMBER(6),
PRONAME NVARCHAR2(50)
);

# 員工表
CREATE TABLE EMPLOYEE(
EMPID NUMBER(6),
EMPNAME NVARCHAR2(50)
);

# 項目員工表
CREATE TABLE PROEMP(
RPROID NUMBER(6),
REMPID NUMBER(6)
);

--添加約束
ALTER TABLE PROJECT
ADD CONSTRAINT PK_PROID PRIMARY KEY (PROID);

ALTER TABLE EMPLOYEE
ADD CONSTRAINT PK_EMPID PRIMARY KEY (EMPID);

ALTER TABLE PROEMP
ADD CONSTRAINT PK_PROEMP PRIMARY KEY (RPROID,REMPID); --聯合主鍵

ALTER TABLE PROEMP
   ADD CONSTRAINT FK_PROID         
     FOREIGN KEY(RPROID) REFERENCES PROJECT(PROID);
     
ALTER TABLE PROEMP
   ADD CONSTRAINT FK_REMPID         
     FOREIGN KEY(REMPID) REFERENCES EMPLOYEE(EMPID);
     
--
SELECT * FROM EMPLOYEE;
SELECT * FROM PROJECT;
SELECT * FROM PROEMP;

  

配置單向多對多關聯

假設Projcet到Employee為單向多對多關聯。

  • Project類中定義員工集合
  • 員工類中不定義和Project相關集合屬性

配置

1.Project類

package com.etc.entity;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/** 項目實體類 */
public class Project implements Serializable {

	private static final long serialVersionUID = 1L;

	private Integer proid;// 項目編號
	private String proname;// 項目名稱
	private Set<Employee> employees = new HashSet<Employee>();// 員工集合

	public Project() {
		super();
	}

	public Project(Integer proid, String proname) {
		super();
		this.proid = proid;
		this.proname = proname;
	}

	public Integer getProid() {
		return proid;
	}

	public void setProid(Integer proid) {
		this.proid = proid;
	}

	public String getProname() {
		return proname;
	}

	public void setProname(String proname) {
		this.proname = proname;
	}

	public Set<Employee> getEmployees() {
		return employees;
	}

	public void setEmployees(Set<Employee> employees) {
		this.employees = employees;
	}
}

  

2.Employee類

package com.etc.entity;

import java.io.Serializable;

/** 員工實體類 */
public class Employee implements Serializable {

	private static final long serialVersionUID = 1L;
	private Integer empid;// 員工編號
	private String empname;// 員工姓名

	public Employee() {
		super();
	}

	public Employee(Integer empid, String empname) {
		super();
		this.empid = empid;
		this.empname = empname;
	}

	public Integer getEmpid() {
		return empid;
	}

	public void setEmpid(Integer empid) {
		this.empid = empid;
	}

	public String getEmpname() {
		return empname;
	}

	public void setEmpname(String empname) {
		this.empname = empname;
	}

}

  

3.Project.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.etc.entity.Project" table="PROJECT" schema="scott"
		dynamic-update="true">
		<id name="proid" type="java.lang.Integer" column="PROID">
			<generator class="assigned" />
		</id>
		<property name="proname" type="java.lang.String" column="PRONAME" />
		<!-- 項目到員工單向多對多關聯 -->
		<set name="employees" table="PROEMP" cascade="save-update">
			<key column="RPROID" />
			<many-to-many class="com.etc.entity.Employee" column="REMPID" />
		</set>
	</class>
</hibernate-mapping>

<set>元素的table屬性指定關系表的名稱為PROEMP。

<set>元素的cascade屬性為save-update,表明保存或更新Project對象時,會級聯保存或更新與它關聯的Employee對象。但是不建議設置為“all”或者“delete”,因為刪除項目的時候可能會導致刪除員工信息。

<set>元素的<key>子元素指定PROEMP的外健RPROID,用來參照PROJECT表。

<many-to-many>子元素的class屬性指定employees集合中存放的是Employee對象,column屬性指定PROEMP表的外鍵REMPID , 用來參照EMPLOYEE表。

  

4.Employee.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.etc.entity.Employee" table="EMPLOYEE" schema="scott"
		dynamic-update="true">
		<id name="empid" type="java.lang.Integer" column="`EMPID`">
			<generator class="assigned" />
		</id>
		<property name="empname" type="java.lang.String" column="EMPNAME" />
	</class>
</hibernate-mapping>

  

持久化操作

創建兩個Project對象和兩個Employee對象,建立他們之間的關聯關系,在保存Project對象的同時保存員工信息。

1.DAO關鍵代碼

	public void save(Project project) throws Exception {
		this.getSession().save(project);
	}

  

2.BIZ關鍵代碼

	public void addNewProject(Project project) {
		Transaction tx = null;
		try {
			tx = projectDao.getSession().beginTransaction();
			projectDao.save(project);
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			if (tx != null) {
				tx.rollback();
			}
		}
	}

  

3.測試方法代碼

@Test
	public void testAdd() {
		Employee employee1=new Employee(1,"張三");
		Employee employee2=new Employee(2,"李四");
		
		Project project1 =new Project(1,"1號項目");
		Project project2 =new Project(2,"2號項目");
		
		project1.getEmployees().add(employee1);
		project1.getEmployees().add(employee2);
		
		project2.getEmployees().add(employee1);
		
		ProjectBiz pb = new ProjectBiz();
		pb.addNewProject(project1);
		pb.addNewProject(project2);
	}	

  

配置雙向多對多關聯

建立從Project類到Employee類的雙向多對多關聯,在Project類中需要定義集合類型的employees屬性,並且在Employee類中也需要定義集合類型的projects屬性。

配置

1.Project類

	private Integer proid;// 項目編號
	private String proname;// 項目名稱
	private Set<Employee> employees = new HashSet<Employee>();// 員工集合

  

2.Employee類

	private Integer empid;// 員工編號
	private String empname;// 員工姓名
	private Set<Project> projects = new HashSet<Project>(); // 項目集合

  

3.Project.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.etc.entity.Project" table="PROJECT" schema="scott"
		dynamic-update="true">
		<id name="proid" type="java.lang.Integer" column="PROID">
			<generator class="assigned" />
		</id>
		<property name="proname" type="java.lang.String" column="PRONAME" />
		<!-- 項目到員工雙向向多對多關聯 -->
		<set name="employees" table="PROEMP" cascade="save-update">
			<key column="RPROID" />
			<many-to-many class="com.etc.entity.Employee" column="REMPID" />
		</set>
	</class>
</hibernate-mapping>

  

4.Employee.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.etc.entity.Employee" table="EMPLOYEE" schema="scott"
		dynamic-update="true">
		<id name="empid" type="java.lang.Integer" column="`EMPID`">
			<generator class="assigned" />
		</id>
		<property name="empname" type="java.lang.String" column="EMPNAME" />
		<!-- 員工到項目多對多關系 -->
		<set name="projects" table="PROEMP" inverse="true">
			<key column="REMPID" />
			<many-to-many class="com.etc.entity.Project" column="PPROID" />
		</set>
	</class>
</hibernate-mapping>

  註意:Employee.hbm.xml的set屬性設置為true。

持久化

1.DAO關鍵代碼

	public void save(Project project) throws Exception {
		this.getSession().save(project);
	}

  

2.BIZ關鍵代碼

	public void addNewProject(Project project) {
		Transaction tx = null;
		try {
			tx = projectDao.getSession().beginTransaction();
			projectDao.save(project);
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			if (tx != null) {
				tx.rollback();
			}
		}
	}

  

3.測試方法關鍵代碼

	@Test
	public void testAdd() {
		Employee employee1=new Employee(1,"張三");
		Employee employee2=new Employee(2,"李四");
		
		Project project1 =new Project(1,"1號項目");
		Project project2 =new Project(2,"2號項目");
		
		project1.getEmployees().add(employee1);
		project1.getEmployees().add(employee2);		
		project2.getEmployees().add(employee1);
		
		employee1.getProjects().add(project1);
		employee1.getProjects().add(project2);
		employee2.getProjects().add(project1);
		
		ProjectBiz pb = new ProjectBiz();
		pb.addNewProject(project1);
		pb.addNewProject(project2);
	}	

  

在實際開發過程中,如果連接表中除了兩個外鍵,還包括其他業務字段,根據業務需要,可以把多對多關聯分解為兩個一對多關聯。

Hibernate-03 關聯映射