上一篇博客实现了一对多单向实现,现在实现双向关联。
一、班级学生一对多映射实现(双向)
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);
}
}
下面写一个测试函数,通过班级端来查找学生:
@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()方法。看一下数据库中生成的结果为: