掌握了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); } }
这种情况就属于自身的双向多对一。