Hibernate-03 关联映射

学习任务

  • 关联映射
  • 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);
	}	

  

在实际开发过程中,如果连接表中除了两个外键,还包括其他业务字段,根据业务需要,可以把多对多关联分解为两个一对多关联。

posted @ 2018-04-03 09:39  rask  阅读(189)  评论(0编辑  收藏  举报