J2EE系列之Hibernate4学习笔记(五)--关联关系一对多映射

xiaoxiao2021-02-27  457

上一篇博客实现了一对多单向实现,现在实现双向关联。

一、班级学生一对多映射实现(双向)

1.一个班级里面多个学生,在班级类中添加学生类集合属性:

package com.test.model; import java.util.HashSet; import java.util.Set; public class Class { private long id; private String name; private Set<Student> students = new HashSet<Student>(); 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; } public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; } } 2.更改Class类的映射文件:

<hibernate-mapping package="com.test.model"> <class name="Class" table="t_class"> <id name="id" column="classId"> <generator class="native"></generator> </id> <property name="name" column="className"></property> <set name="students" cascade="save-update"> <key column="classId"></key> <!-- 对应的外键 --> <one-to-many class="com.test.model.Student"/> </set> </class> </hibernate-mapping>

这里使用<set>标签进行集合对象的标记,这里要注意设置cascade的值为save-update才能完成级联保存,通过保存班级信息级联完成学生信息的保存。里面<key>标签标记student类中的外键列,<one-to-many>标记级联的学生类。

3.新建测试类StudentTest:

package com.test.service; import java.util.Iterator; import java.util.Set; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.test.model.Class; import com.test.model.Student; import com.test.util.HibernateUtil; public class StudentTest { private SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); // 获取Session工厂 private Session session; @Before public void setUp() throws Exception { session=sessionFactory.openSession(); // 生成一个session session.beginTransaction(); // 开启事务 } @After public void tearDown() throws Exception { session.getTransaction().commit(); // 提交事务 session.close(); // 关闭session } @Test public void testClassAndStudentSave() { Class c = new Class(); c.setName("08数学"); Student s1 = new Student(); s1.setName("张三"); Student s2 = new Student(); s2.setName("李四"); c.getStudents().add(s1); c.getStudents().add(s2); session.save(c); } }

这个测试函数中学生对象s1、s2是临时对象,没有保存在session缓存中,只把新建的班级对象c保存在了session缓存中。运行测试函数发现班级信息以及学生信息都保存在了数据库中,这里级联保存发挥了作用。这个测试函数通过班级端保存了学生端的信息。

下面写一个测试函数,通过班级端来查找学生:

@Test public void getStudentsByClass(){ Class c = (Class)session.get(Class.class, Long.valueOf(1)); Set<Student> students = c.getStudents(); Iterator<Student> it = students.iterator(); while(it.hasNext()){ Student s = (Student)it.next(); System.out.println(s); } } 运行测试函数,控制台输出为:

这里查找到了该班级下面的所有学生信息。可以看到hibernate一共执行了两条sql语句,第一条是查找主键为1的班级信息,第二条这是查找该班级下面的学生信息。

这里就是双向实现的好处,可以从任意一端实现保存和查找另一端的信息。

二、inverse属性:它是用来指定关联的控制方的。inverse属性默认是false,若为false,则关联由自己控制,若为true,则关联由对方控制。

1.把之前的数据表t_student,t_class删除,写一个测试样例:

@Test public void testAdd(){ Class c = new Class(); c.setName("09数学"); Student s1 = new Student(); s1.setName("王五"); session.save(c); session.save(s1); } 运行这个测试例子,在数据库中添加一条学生信息,一条班级信息:

    

此时的班级和学生是没有任何关系的。

2.写一个测试函数如下:

@Test public void testInverse(){ Class c = (Class)session.get(Class.class, Long.valueOf(1)); Student s = (Student)session.get(Student.class, Long.valueOf(1)); s.setC(c); //c.getStudents().add(s); } 这里获取到班级类和学生类,下面的两条语句一个是学生类中设置班级信息,一个是班级信息中添加学生信息,这两条语句不论运行哪一条,由于实现了双向级联,结果都是一样的,都会把另一方的信息也保存在数据库中,都会把t_student表格中学生信息的classId一列赋值。也就是说不论运行哪一条,都会起到同时运行这两条语句的作用。也就是说班级和学生这两方都会维护数据库的级联关系。现在我们使用inverse属性,这个属性可以设置当前一方是否维护级联关系。修改class类的映射文件为:

<hibernate-mapping package="com.test.model"> <class name="Class" table="t_class"> <id name="id" column="classId"> <generator class="native"></generator> </id> <property name="name" column="className"></property> <set name="students" cascade="save-update" inverse="true"> <key column="classId"></key> <!-- 对应的外键 --> <one-to-many class="com.test.model.Student"/> </set> </class> </hibernate-mapping> 这里设置了inverse属性为true,也就是权利翻转,即自己不再控制关联关系。进行了上述设置后,关联关系有Student类来维护,而Class类不再维护关联关系。

3.删除数据库中的表格,重新运行testAdd函数,此时班级信息和学生信息是没有关系的。运行testInverse函数:

可见,这里进行了级联关系,更新了t_student表中信息:

4.删除t_student、t_class表格,重新运行testAdd函数。修改testInverse并运行:

@Test public void testInverse(){ Class c = (Class)session.get(Class.class, Long.valueOf(1)); Student s = (Student)session.get(Student.class, Long.valueOf(1)); //s.setC(c); c.getStudents().add(s); } 此时从班级端添加学生信息,由于此时已经设置了inverse属性为true,也就是班级端不再维护关联关系。这时运行结果为:

三、级联删除

这里要实现删除班级的时候把里面的班级信息一起删除。写测试函数:

@Test public void testDeleteClassCascade(){ Class c = (Class)session.get(Class.class, Long.valueOf(1)); session.delete(c); } 运行测试函数,程序运行失败,报错为有外键关联不能删除。这里class的主键是被student类的外键关联的,当hibernate检测到这个结果后是不能删除class类信息的。这里可以通过配置实现。修改class映射文件为:

<hibernate-mapping package="com.test.model"> <class name="Class" table="t_class"> <id name="id" column="classId"> <generator class="native"></generator> </id> <property name="name" column="className"></property> <set name="students" cascade="delete" inverse="true"> <key column="classId"></key> <!-- 对应的外键 --> <one-to-many class="com.test.model.Student"/> </set> </class> </hibernate-mapping> 这里把cascade的属性值设置为了delete,再次运行程序成功实现了级联删除。

四、一对多双向自身关联关系映射

这里说一下什么是自身关联关系呢??举个例子,每个一级菜单可以有多个二级菜单,每个二级菜单又可以有多个三级菜单,这里菜单就是一个自身关联映射。每个二级菜单有一个父菜单,有多个子菜单。这里是一个一对多映射关系。废话少说,看下面的示例。

1.新建一个Node类:

package com.test.model; import java.util.HashSet; import java.util.Set; public class Node { private int id; private String name; private Node parentNode; private Set<Node> childNodes = new HashSet<Node>(); public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Node getParentNode() { return parentNode; } public void setParentNode(Node parentNode) { this.parentNode = parentNode; } public Set<Node> getChildNodes() { return childNodes; } public void setChildNodes(Set<Node> childNodes) { this.childNodes = childNodes; } }

这里每个节点既是子节点又是父节点。

2.新建Node类的映射文件:

<?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.test.model"> <class name="Node" table="t_node"> <id name="id" column="nodeId"> <generator class="native"></generator> </id> <property name="name" column="nodeName"></property> <many-to-one name="parentNode" column="parentId" class="com.test.model.Node" cascade="save-update"></many-to-one> <set name="childNodes" inverse="true"> <key column="parentId"></key> <!-- 对应的外键 --> <one-to-many class="com.test.model.Node"/> </set> </class> </hibernate-mapping> 这里每个节点既是子节点又是父节点。

3.把这个映射文件添加到配置文件中。

4.新建一个Junit测试类:

package com.test.service; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.test.model.Node; import com.test.model.Student; import com.test.util.HibernateUtil; public class NodeTest { private SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); // 获取Session工厂 private Session session; @Before public void setUp() throws Exception { session=sessionFactory.openSession(); // 生成一个session session.beginTransaction(); // 开启事务 } @After public void tearDown() throws Exception { session.getTransaction().commit(); // 提交事务 session.close(); // 关闭session } @Test public void testSaveMenu() { Node node = new Node(); node.setName("根节点"); Node subNode1 = new Node(); subNode1.setName("自节点1"); Node subNode2 = new Node(); subNode2.setName("自节点2"); subNode1.setParentNode(node); subNode2.setParentNode(node); session.save(subNode1); session.save(subNode2); } } 5.运行testSaveMenu()方法。看一下数据库中生成的结果为:

转载请注明原文地址: https://www.6miu.com/read-3713.html

最新回复(0)