四、Hibernate关联关系一对多映射

掌握了Hibernate框架的搭建及基本的CRUD,我们从实例中来探讨Hibernate的关联关系。

一对多(单向)

采用 学生——班级 的对应关系 : 班级一  学生多 

1.先创建学生与班级的实体对象及其映射文件:

班级Class.java

package com.tao.entity;

/**
 * 班级实体类 
 * 关系是 一
 * @author TaoGG
 *
 */
public class Class {
	private long classId;
	private String className;
	
	public long getClassId() {
		return classId;
	}
	public void setClassId(long classId) {
		this.classId = classId;
	}
	public String getClassName() {
		return className;
	}
	public void setClassName(String className) {
		this.className = className;
	}
}

Class.hbm.xml: 

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.tao.entity">
	<class name="Class" table="t_class">
		<id name="classId" column="class_id">
			<generator class="native" />
		</id>
		
		<property name="className" column="class_name" length="50" />
	
	</class>

</hibernate-mapping>

学生Student.class:

注意:我们采用在多的一方 维护一个 “一的一方” 的对象 也就是在 学生实体中 维护一个班级,理解成学生属于哪个班级

如果是在一的一方维护 那就需要维护一个学生的集合 理解成一个班有哪些学生,这个在后续 会讲到。

package com.tao.entity;
/**
 * 学生实体类
 * 关系是 多 
 * 实现单向关联 我们在多的一方维护一个对象
 * @author TaoGG
 *
 */
public class Student {
	
	private Long id;
	private String name;
	private Class c;
	
	public Class getC() {
		return c;
	}

	public void setC(Class c) {
		this.c = c;
	}

	public Long getId() {
		return id;
	}
	
	public void setId(Long id) {
		this.id = id;
	}
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + "]";
	}
	

}

Student.hbm.xml:

使用 <many-to-one>  进行一对多配置,注意要在多的一方维护一个 “一的一方” 的外键。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.tao.entity">
	<class name="Student" table="t_student">
		<id name="id" column="stu_id">
			<generator class="identity" />
		</id>
		
		<property name="name" column="stu_name" length="50" />
		
		<!-- 实现多对一的关联 指定外键和实体类 cascade 默认none -->
		<many-to-one name="c" column="c_id" class="com.tao.entity.Class" ></many-to-one>
	
	</class>

</hibernate-mapping>

Hibernate核心配置文件:

<?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>
		<!-- 配置关于数据库连接的四个项:driverClass  url username password -->
		<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="connection.url">jdbc:mysql://localhost:3306/hibernate</property>
		<property name="connection.username">root</property>
        <property name="connection.password">root</property>
        
       <!-- 方言 -->
	   <property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
	
	   <!-- 控制台显示SQL -->
	   <property name="show_sql">true</property>
	
	   <!-- 自动更新表结构 -->
	   <property name="hbm2ddl.auto">update</property>
	   
	   <!-- 引入的映射文件 -->
	   <mapping resource="com\tao\entity\Class.hbm.xml"/>	
	   <mapping resource="com\tao\entity\Student.hbm.xml"/>
		   	   
	</session-factory>
	
</hibernate-configuration>

测试类:

package com.tao.test;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.tao.entity.Class;
import com.tao.entity.Student;
import com.tao.util.HibernateUtil;

public class TestManyToOne {

	SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
	private Session session;

	@Before
	public void setUp() throws Exception {
		session = sessionFactory.openSession();
		session.beginTransaction();
	}

	@After
	public void tearDown() throws Exception {
		session.getTransaction().commit();
		session.close();
	}

	@Test
	public void testSavaClassAndStudent() {
		
		Class c = new Class();
		c.setClassName("0804班");
		
		Student s1 = new Student();
		s1.setName("凌志颖");
		s1.setC(c);
		
		Student s2 = new Student();
		s2.setName("周隆兴");
		s2.setC(c);
		
		session.save(c);
		session.save(s1);
		session.save(s2);
		
	}
	
	@Test
	public void testSavaClassAndStudentWithCascade() {
		
		Class c = new Class();	//瞬时状态 或 叫做临时对象
		c.setClassName("0804班");
		
		Student s1 = new Student();
		s1.setName("苍井空");
		s1.setC(c);
		
		Student s2 = new Student();
		s2.setName("泷泽萝拉");
		s2.setC(c);
		
		session.save(s1);	//保存后  变持久化状态 发放session缓存中
		session.save(s2);
		
	}

}

运行单元测试方法,执行三条SQL语句,可查看数据库结果 保存成功。

有个问题 就是我们上面的代码使用了 session.save() 保存了 两个学生 还单独保存了班级。

可我们在学生实体中 已经设置了  该学生属于哪个班级,可不可以设置后 在保存学生后 自动级联保存班级呢?

我们先试一下 去掉 session.save(c); 这句代码:

报错:Object 引用了一个未保存的瞬时对象,也就是Student 引用了Class 但Class未保存 导致该错误。

怎么解决呢们这边就需要用到级联了:

在<many-to-one>这端,cascade 默认是”none”,假如我们希望在持久化多的一端的时候,自动级
联保存和更新一的一端,我们可以把 cascade 设置成”save-update”

我们在 Student.hbm.xml 文件中 开启级联保存:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.tao.entity">
	<class name="Student" table="t_student">
		<id name="id" column="stu_id">
			<generator class="identity" />
		</id>
		
		<property name="name" column="stu_name" length="50" />
		
		<!-- 实现多对一的关联 指定外键和实体类 cascade 默认none 不级联保存 save-update 级联保存-->
		<many-to-one name="c" column="c_id" class="com.tao.entity.Class" cascade="save-update"></many-to-one>
	
	</class>

</hibernate-mapping>

再执行一遍方法: 保存成功。

上面我们知道了怎么在多的一方维护关系,下面我们看下怎么在一的一方维护关系,实现双向的 一对多。

我们在班级实体 维护一个学生的Set集合:

package com.tao.entity;

import java.util.HashSet;
import java.util.Set;

/**
 * 班级实体类 
 * 关系是 一
 * @author TaoGG
 *
 */
public class Class {
	private long classId;
	private String className;
	private Set<Student> students = new HashSet<Student>();
	
	public Set<Student> getStudents() {
		return students;
	}
	public void setStudents(Set<Student> students) {
		this.students = students;
	}
	public long getClassId() {
		return classId;
	}
	public void setClassId(long classId) {
		this.classId = classId;
	}
	public String getClassName() {
		return className;
	}
	public void setClassName(String className) {
		this.className = className;
	}
}

然后在Class.hbm.xml 中 对 "一的一方" 配置 一对多关系:

注意:这边维护的key 就是在多的一方维护的key 通过外键 实现双向 多对一

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.tao.entity">
	<class name="Class" table="t_class">
		<id name="classId" column="class_id">
			<generator class="native" />
		</id>
		
		<property name="className" column="class_name" length="50" />
		
		<!-- 配置一对多关系 在班级 配置学生 -->
		<set name="students" cascade="save-update">
			<key column="c_id"></key>		
			<one-to-many class="com.tao.entity.Student"/>
		</set>
	
	</class>

</hibernate-mapping>

进行一下单元测试 

通过班级保存学生:

/**
	 * 通过班级保存学生
	 */
	@Test
	public void testSavaClassAndStudent() {
		
		Class c = new Class();
		c.setClassName("0804班");
		
		Student s1 = new Student();
		s1.setName("凌志颖");
		
		Student s2 = new Student();
		s2.setName("周隆兴");
		
		//通过班级来保存学生
		c.getStudents().add(s1);
		c.getStudents().add(s2);
		session.save(c); 
	}

控制台:

数据库中成功插入数据,且有正确的对应关系,实现了双向 多对一

我们再试一下 通过班级 查询当前班级有哪些学生 这是实现双向多对一主要要做的事情:

新增一个单元测试方法:

/**
	 * 通过班级 查找学生
	 */
	@Test
	public void queryStudentByClass() {
		Class c = (Class)session.get(Class.class, Long.valueOf(1));
		Set<Student> students = c.getStudents();
		for(Iterator<Student> iter = students.iterator();iter.hasNext();){
			Student stu = iter.next();
			System.out.println(stu);
		}
	}

发现数据可以正常查询 至此 双向多对一完成。

Inverse属性:

Hibernate中从session中取回的数据为持久化数据,我们怎么为持久化数据设置关联关系呢?

正常的我们是这样:

我们先给数据库设置两条数据:

@Test
	public void testAdd(){
		Class c = new Class();
		c.setClassName("0808班");
		
		Student student = new Student();
		student.setName("张三");
		
		session.save(c);
		session.save(student);
		
	}

然后为其设置对应关系:

@Test
	public void testAddInverse(){
		//从session中获取 持久化对象
		Class c = (Class) session.get(Class.class, Long.valueOf(1));
		Student s = (Student) session.get(Student.class, Long.valueOf(1));
		
		//对持久化对象 互相设置关系 Hinernate 检测到 会同步数据库操作 但这样做 会出现冗余SQL 
		s.setC(c);
		c.getStudents().add(s);
	}

看控制台SQL:它的实际执行流程是先去设置学生,然后再为学生设置关系。

类似于先执行了:s.setC(c); 然后又执行:c.getStudents().add(s);

实质一条SQL可以执行的,但是又执行了一条SQL,当数据量大的时候 是不利于系统的。

所以 这个时候 Inverse 就排上用场了 我们在 "一的一方"  把关系交给多的一方维护:

设置 inverse="true"

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.tao.entity">
	<class name="Class" table="t_class">
		<id name="classId" column="class_id">
			<generator class="native" />
		</id>
		
		<property name="className" column="class_name" length="50" />
		
		<!-- 配置一对多关系 在班级 配置学生   
		     inverse把控制权让给对方来维护关联关系(一般采用多的一方来维护 因为经常用)
		              如果控制权在对方手上 也就是说让对方维护关系 那么自己的级联就失效  控制权在哪方哪方级联有效-->
		<set name="students" cascade="save-update" inverse="true">
			<key column="c_id"></key>		
			<one-to-many class="com.tao.entity.Student"/>
		</set>
	
	</class>

</hibernate-mapping>

那这边就只需要:学生进行设置就好了 

关于Inverse属性的理解 我这边不做过多说明,可以参考cascade(级联)和inverse关系详解

有点要注意的是 如果哪一方设置了inverse 那这一方的级联控制权限就失效了。 可以理解一下。

@Test
	public void testAddInverse(){
		//从session中获取 持久化对象
		Class c = (Class) session.get(Class.class, Long.valueOf(1));
		Student s = (Student) session.get(Student.class, Long.valueOf(1));
		
		//对持久化对象 互相设置关系 Hinernate 检测到 会同步数据库操作 但这样做 会出现冗余SQL 
		s.setC(c);
	}

自身一对多,类似于菜单节点,功能目录的结构。一般采取把自身先当作 "多的一方" 维护一个一的一方的对象;然后再把自己当作 "一的一方" 维护一个多的一方的集合。

Node.java :

/**
 * 当前结点和 父节点 多对一
 * 自身是父节点 一对多
 * @author TaoGG
 *
 */
public class Node {
	
	private long nodeId;
	private String nodeName;
	private Node parentNode;	//父节点 
	private Set<Node> childNodes = new HashSet<Node>();	//子节点
	
	
	public Set<Node> getChildNodes() {
		return childNodes;
	}
	public void setChildNodes(Set<Node> childNodes) {
		this.childNodes = childNodes;
	}
	public Node getParentNode() {
		return parentNode;
	}
	public void setParentNode(Node parentNode) {
		this.parentNode = parentNode;
	}
	public long getNodeId() {
		return nodeId;
	}
	public void setNodeId(long nodeId) {
		this.nodeId = nodeId;
	}
	public String getNodeName() {
		return nodeName;
	}
	public void setNodeName(String nodeName) {
		this.nodeName = nodeName;
	}
	@Override
	public String toString() {
		return "Node [nodeId=" + nodeId + ", nodeName=" + nodeName + ", parentNode=" + parentNode + "]";
	}
	
}

Node.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.tao.entity">
	<class name="Node" table="t_node">
		<id name="nodeId" column="node_id">
			<generator class="native" />
		</id>
		
		<property name="nodeName" column="node_name" length="100" />
		
		<!-- 把自己当作多的一方 配置many to one -->
		<many-to-one name="parentNode" column="parent_nodeId" class="com.tao.entity.Node" cascade="save-update"></many-to-one>
	
		<!-- 把自己当作 一的一方 -->
		<set name="childNodes" inverse="true">
			<key column="parent_nodeId"></key>
			<one-to-many class="com.tao.entity.Node"/>
		</set>
	</class>

</hibernate-mapping>

Test method:


	@Test
	public void testNodeSaveMenu() throws Exception {
		Node root = new Node();
		root.setNodeName("根节点");
		
		Node subNode1 = new Node();
		subNode1.setNodeName("子节点1");
		Node subNode2 = new Node();
		subNode2.setNodeName("子节点2");
		
		subNode1.setParentNode(root);
		subNode2.setParentNode(root);
		
		session.save(subNode1);
		session.save(subNode2);
	}
	
	@Test
	public void testQueryMenu(){
		Node n = (Node) session.get(Node.class, Long.valueOf(1));
		Set<Node> childNodes = n.getChildNodes();
		for(Node c:childNodes){
			System.out.println(c);
		}
	}

这种情况就属于自身的双向多对一。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值