(转)Hibernate框架基础——一对多关联关系映射
http://blog.csdn.net/yerenyuan_pku/article/details/52746413
上一篇文章Hibernate框架基础——映射集合属性详细讲解的是值类型的集合(即集合元素是普通类型)。现在从本文开始我们就要介绍实体类型的集合(即集合元素是另一个实体)了。
一对多关联关系映射
我们还是以一个活生生的例子来详解一对多关联关系映射吧!就以部门和员工的关系为例。
- 单向关联:仅仅建立从Employee到Department的多对一关联,即仅仅在Employee类中定义department属性。或者仅仅建立从Department到Employee的一对多关联,即仅仅在Department类中定义employees集合。
- 双向关联:既建立从Employee到Department的多对一关联,又建立从Department到Employee的一对多关联。
单向多对一
单向n-1关联只需从n的一端可以访问1的一端。
从Employee到Department的多对一单向关联需要在Employee类中定义一个Department属性,而在Department类中无需定义存放Employee对象的集合属性。接下来我们理应要创建出这两个持久化类了。
我们最好新建一个普通java工程,如Hibernate_Test,然后在cn.itcast.f_hbm_oneToManyb包下新建持久化类——Department.java和Employee.java。
持久化类——Department.java的代码如下:
/**
* 部门
* @author li ayun
*
*/
public class Department {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "[Department: id=" + id + ", name=" + name + "]";
}
}
持久化类——Employee.java的代码如下:
/**
* 员工
* @author li ayun
*
*/
public class Employee {
private Integer id;
private String name;
private Department department; // 关联的部门对象
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
@Override
public String toString() {
return "[Employee: id=" + id + ", name=" + name + "]";
}
}
接着我们就要创建各个持久化类相应的映射配置文件了。但在做之前,我们约莫是要知道数据库中两张表的结构的,我画图表示:
现在再来写各个持久化类相应的映射配置文件,我想应该会容易得多。先在cn.itcast.f_hbm_oneToMany包中创建Department类对应的映射配置文件——Department.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="cn.itcast.f_hbm_oneToMany">
<class name="Department" table="department">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name" />
</class>
</hibernate-mapping>
然后也是在该包中创建Employee类对应的映射配置文件——Employee.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="cn.itcast.f_hbm_oneToMany">
<class name="Employee" table="employee">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name" type="string" column="name" />
<!--
department属性,表达的是本类与Department的多对一的关系
class属性:关联的实体类型
column属性:外键列(引用关联对象的表的主键)
-->
<many-to-one name="department" class="Department" column="departmentId"></many-to-one>
</class>
</hibernate-mapping>
many-to-one属性:
- name:设定待映射的持久化类的名字。
- column:设定和持久化类的属性对应的表的外键。
- class:设定持久化类的属性的类型。
接下来,我们从以下几个方面来编写代码进行测试:
- 保存新数据,并有关联关系。
- 获取数据,可以从多方获取到一方,但从一方获取不到多方。
- 解除关联关系。
- 删除对象,看对关联对象的影响。
所以,我们要在cn.itcast.f_hbm_oneToMany包中编写一个单元测试类——Application.java。
public class Application {
private static SessionFactory sessionFactory = new Configuration() //
.configure() //
.addClass(Department.class) // 添加Hibernate实体类(加载对应的映射文件)
.addClass(Employee.class) // 添加Hibernate实体类(加载对应的映射文件)
.buildSessionFactory();
// 保存,有关联关系
@Test
public void testSave() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// -------------------------------------------
// 新建对象
Department department = new Department();
department.setName("开发部");
Employee employee1 = new Employee();
employee1.setName("张三");
Employee employee2 = new Employee();
employee2.setName("李四");
// 关联起来
employee1.setDepartment(department); // 告诉员工他属于哪个部门
employee2.setDepartment(department);
// 保存
session.save(department); // 保存部门,记住:被依赖的一般放在前面保存,需要依赖别人的一般放在后面保存
session.save(employee1);
session.save(employee2);
// -------------------------------------------
session.getTransaction().commit();
session.close();
}
// 获取,可以获取到关联的对方
@Test
public void testGet() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// -------------------------------------------
// 获取一方,并显示另一方信息
Employee employee = (Employee) session.get(Employee.class, 1);
System.out.println(employee);
System.out.println(employee.getDepartment());
// -------------------------------------------
session.getTransaction().commit();
session.close();
}
// 解除关联关系
@Test
public void testRemoveRelation() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// -------------------------------------------
// 从员工方解除
Employee employee = (Employee) session.get(Employee.class, 1);
employee.setDepartment(null);
// -------------------------------------------
session.getTransaction().commit();
session.close();
}
// 删除对象,对关联对象的影响
@Test
public void testDelete() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// -------------------------------------------
// 删除员工方(多方),对对方没有影响
Employee employee = (Employee) session.get(Employee.class, 1);
session.delete(employee);
// -------------------------------------------
session.getTransaction().commit();
session.close();
}
}
测试,顺利通过,大发!
单向一对多
单向1–n关联只需从1的一端可以访问n的一端。在实际开发里面,关于一对多和多对一,有一个设计原则:尽量避免去使用一对多这种关系,而应该多使用多对一这种关系。但有时候是不可避免的,例如,订单和订单项就是一对多这种关系,一个订单里面有多个订单项,在显示订单的时候是一定要显示多方的数据的,即订单项,这时候我们就必须设计这种关系了。如果你没有设计这种关系,这时候从数据库里面找出订单的基本信息,人家一看订单,只能看到订单的基本信息,不能看到订单项,这是不行的。如果这时候不可避免地要设计这种关系(一对多这种关系),并且订单项有上千万条的数据,那么千万不能直接将其找出来,一定要采用分页的方式去找。
所以,单向一对多的这种关联关系,我就不打算介绍了。
一对多/多对一双向关联
在一方记住了多方,在多方也记住了一方,我们这样设计就是双向的关联,在实际开发里面,这种双向的关联是不推荐使用的!而且一对多这种关系最好就不要设计,即不要在一方记住了多方,如果你设计了,在找出一方的数据时就要取出多方的数据,如果多方的数据超多的话,很容易导致内存溢出。
虽然在实际开发里面,一对多/多对一双向关联不推荐使用,但是我们还是应该有所了解的。为了介绍这种关联关系,我们应该修改持久化类——Department.java的代码为:
/**
* 部门
* @author li ayun
*
*/
public class Department {
private Integer id;
private String name;
private Set<Employee> employees = new HashSet<Employee>(); // 关联的很多个员工
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Employee> getEmployees() {
return employees;
}
public void setEmployees(Set<Employee> employees) {
this.employees = employees;
}
@Override
public String toString() {
return "[Department: id=" + id + ", name=" + name + "]";
}
}
相应地,接下来应该修改该类所对应的映射配置文件——Department.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="cn.itcast.f_hbm_oneToMany">
<class name="Department" table="department">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name" />
<!--
employees属性,Set集合,表达的是本类与Employee的一对多的关系
class属性:关联的实体类型
key子元素:对方表中的外键列(多方的哪个表)
-->
<set name="employees">
<key column="departmentId"></key>
<one-to-many class="Employee"/>
</set>
</class>
</hibernate-mapping>
<set>
元素来映射持久化类的set类型的属性。
- name:设定待映射持久化类的属性名。
- key子属性:设定与所关联的持久化类对应的表的外键。
- column:指定关联表的外键名。
- one-to-many子属性:设定所关联的持久化类(集合中存放的对象)。
- class:指定关联的持久化类的类名。
接下来,我们从以下几个方面来编写代码进行测试:
- 保存新数据,并有关联关系。
- 获取数据,可以获取到关联的对方。
- 解除关联关系。
- 删除对象,看对关联对象的影响。
我们先从保存新数据,并有关联关系这一个方面开始测试,测试代码如下:
public class Application {
private static SessionFactory sessionFactory = new Configuration() //
.configure() //
.addClass(Department.class) // 添加Hibernate实体类(加载对应的映射文件)
.addClass(Employee.class) // 添加Hibernate实体类(加载对应的映射文件)
.buildSessionFactory();
// 保存,有关联关系
@Test
public void testSave() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// -------------------------------------------
// 新建对象
Department department = new Department();
department.setName("开发部");
Employee employee1 = new Employee();
employee1.setName("张三");
Employee employee2 = new Employee();
employee2.setName("李四");
// 关联起来
employee1.setDepartment(department); // 告诉员工他属于哪个部门
employee2.setDepartment(department);
department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
department.getEmployees().add(employee2);
// 保存
session.save(department); // 保存部门,记住:被依赖的一般放在前面保存,需要依赖别人的一般放在后面保存
session.save(employee1);
session.save(employee2);
// -------------------------------------------
session.getTransaction().commit();
session.close();
}
}
此时测试testSave()方法,Eclipse的控制台打印如下的sql语句:
Hibernate: insert into department (name) values (?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
Hibernate: update employee set departmentId=? where id=?
Hibernate: update employee set departmentId=? where id=?
其中,我们推测这两句sql语句
Hibernate: update employee set departmentId=? where id=?
Hibernate: update employee set departmentId=? where id=?
是由这两句代码
department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
department.getEmployees().add(employee2);
产生的。
如果我们将以上代码注释掉,则就不会产生两条多余的update语句。但是我们在写代码的时候,双方都知道对方的存在会比较好一点,即需要关联双方:
employee1.setDepartment(department); // 告诉员工他属于哪个部门
employee2.setDepartment(department);
department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
department.getEmployees().add(employee2);
这个时候我们又不希望产生那两条多余的update语句,那就需要引入inverse属性了。
inverse属性:
在hibernate中通过对inverse属性的值决定是由双向关联的哪一方来维护表和表之间的关系。inverse=false的为主动方,inverse=true的为被动方,由主动方负责维护关联关系。
在没有设置inverse=true的情况下,父子两边都维护父子关系。
在1-n关系中,将n方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)。
在1-n关系中,若将1方设为主控方会额外多出update语句。插入数据时无法同时插入外键列,因而无法为外键列添加非空约束。
引入inverse属性之后,持久化类(Department.java)对应的映射配置文件——Department.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="cn.itcast.f_hbm_oneToMany">
<class name="Department" table="department">
<id name="id">
<generator class="native"></generator>
</id>
<property name="name" />
<!--
employees属性,Set集合,表达的是本类与Employee的一对多的关系
class属性:关联的实体类型
key子元素:对方表中的外键列(多方的哪个表)
inverse属性:
默认为false,表示本方维护关联关系。
如果为true,表示本方不维护关联关系。
只是影响是否能设置外键列的值(设成有效值或是null值),对获取信息没有任何影响。
-->
<set name="employees" inverse="true">
<key column="departmentId"></key>
<one-to-many class="Employee"/>
</set>
</class>
</hibernate-mapping>
此时,再次执行testSave()方法,Eclipse的控制台就会打印如下的sql语句:
Hibernate: insert into department (name) values (?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
发现那两条多余的update语句没有了。
这时,如若我们注释掉如下两句代码:
department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
department.getEmployees().add(employee2);
Eclipse的控制台仍将打印同样的sql语句:
Hibernate: insert into department (name) values (?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
Hibernate: insert into employee (name, departmentId) values (?, ?)
结论:
- 在映射一对多的双向关联关系时,应该在one方把inverse属性设为true,这可以提高性能。
在建立两个对象的关联时,应该同时修改关联两端的相应属性:
employee1.setDepartment(department); // 告诉员工他属于哪个部门
employee2.setDepartment(department);
department.getEmployees().add(employee1); // 告诉部门它有两个关联的员工
department.getEmployees().add(employee2);这样才会使程序更加健壮,提高业务逻辑层的独立性,使业务逻辑层的程序代码不受Hibernate实现类的影响。同理,当删除双向关联的关系时,也应该修改关联两端的对象的相应属性:
department.getEmployees().clear(); // 清空
employee.setDepartment(null);- 只有集合标记(set/map/list/array/bag)才有inverse属性。
- 维护关联关系是指设置外键列的值(设成有效值或是null值)。
接下来我们从获取数据,可以获取到关联的对方第二个方面来进行测试。
public class Application {
private static SessionFactory sessionFactory = ...
// 获取,可以获取到关联的对方
@Test
public void testGet() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// -------------------------------------------
// 获取一方,并显示另一方信息
Department department = (Department) session.get(Department.class, 1);
System.out.println(department);
System.out.println(department.getEmployees());
// Employee employee = (Employee) session.get(Employee.class, 1);
// System.out.println(employee);
// System.out.println(employee.getDepartment());
// -------------------------------------------
session.getTransaction().commit();
session.close();
}
}
结论是:无论是从哪一方获取数据,都能获取到关联对方的数据。
紧接着我们从解除关联关系第三个方面来进行测试。
从员工方解除
public class Application {
private static SessionFactory sessionFactory = ... // 解除关联关系
@Test
public void testRemoveRelation() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// ------------------------------------------- // 从员工方解除
Employee employee = (Employee) session.get(Employee.class, 1);
employee.setDepartment(null); // -------------------------------------------
session.getTransaction().commit();
session.close();
}
}测试,顺利通过,大发!
从部门方解除
与inverse属性的值有关系,只有inverse为false时才可以解除。public class Application {
private static SessionFactory sessionFactory = ... // 解除关联关系
@Test
public void testRemoveRelation() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// ------------------------------------------- // 从部门方解除(与inverse有关系,inverse为false时可以解除)
Department department = (Department) session.get(Department.class, 1);
department.getEmployees().clear(); // 清空 // -------------------------------------------
session.getTransaction().commit();
session.close();
}
}
最后,我们从删除对象,看对关联对象的影响第四个方面来进行测试。
删除员工方(多方),对对方没有影响
public class Application {
private static SessionFactory sessionFactory = ... // 解除关联关系
@Test
public void testRemoveRelation() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// ------------------------------------------- // 删除员工方(多方),对对方没有影响
Employee employee = (Employee) session.get(Employee.class, 1);
session.delete(employee); // -------------------------------------------
session.getTransaction().commit();
session.close();
}
}删除部门方(一方)
a,如果没有关联的员工:能删除。
b,如果有关联的员工且inverse=true,由于不能维护关联关系,所以直接执行删除,就会有异常。
c,如果有关联的员工且inverse=false,由于可以维护关联关系,它就会先把关联的员工的外键列设为null值,再删除自己。public class Application {
private static SessionFactory sessionFactory = ... // 解除关联关系
@Test
public void testRemoveRelation() {
Session session = sessionFactory.openSession();
session.beginTransaction();
// ------------------------------------------- // 删除部门方(一方)
// a,如果没有关联的员工:能删除
// b,如果有关联的员工且inverse=true,由于不能维护关联关系,所以会直接执行删除,就会有异常
// c,如果有关联的员工且inverse=false,由于可以维护关联关系,它就会先把关联的员工的外键列设为null值,再删除自己。
Department department = (Department) session.get(Department.class, 1);
session.delete(department); // -------------------------------------------
session.getTransaction().commit();
session.close();
}
}
(转)Hibernate框架基础——一对多关联关系映射的更多相关文章
- (转)Hibernate框架基础——多对多关联关系映射
http://blog.csdn.net/yerenyuan_pku/article/details/52756536 多对多关联关系映射 多对多的实体关系模型也是很常见的,比如学生和课程的关系.一个 ...
- SSH:Hibernate框架(七种关联关系映射及配置详解)
概念 基本映射是对一个实体进行映射,关联映射就是处理多个实体之间的关系,将关联关系映射到数据库中,所谓的关联关系在对象模型中有一个或多个引用. 分类 关联关系分为上述七种,但是由于相互之间有各种关系, ...
- Hibernate框架基础
Hibernate框架基础 Hibernate框架 ORM概念 O, Object 对象 R, Realtion 关系 (关系型数据库: MySQL, Oracle…) M,Mapping 映射 OR ...
- (转)Hibernate框架基础——cascade属性
http://blog.csdn.net/yerenyuan_pku/article/details/52760010 我们以部门和员工的关系为例讲解一对多关联关系映射时,删除部门时,如果部门有关联的 ...
- mybatis一对多关联关系映射
mybatis一对多关联关系映射 一对多关联关系只需要在多的一方引入少的一方的主键作为外键即可.在实体类中就是反过来,在少的一方添加多的一方,声明一个List 属性名 作为少的一方的属性. 用户和订单 ...
- (转)Hibernate框架基础——Java对象持久化概述
http://blog.csdn.net/yerenyuan_pku/article/details/52732990 Java对象持久化概述 应用程序的分层体系结构 基于B/S的典型三层架构 说明 ...
- hibernate实体xml一对多关系映射
单向一对多关系映射: 一个房间对应多个使用者,也就是Room實例知道User實例的存在,而User實例則沒有意識到Room實例. 用户表: package onlyfun.caterpillar; p ...
- (转)Hibernate框架基础——映射主键属性
http://blog.csdn.net/yerenyuan_pku/article/details/52740744 本文我们学习映射文件中的主键属性,废话不多说,直接开干. 我们首先在cn.itc ...
- hibernate 的一对多关联关系映射配置
hibernate 是操作实体类: 表是一对多的关系,当创建这2个实体的时候 在一的一方定义一个多的一方的集合 在多的一方定义一个一的一方的对象 表是多对多的关系,当创建这2个实体的时候 在互相中都有 ...
随机推荐
- oralce之复杂查询举例
表结构: S(SNO,SNAME) 代表 学号.学生姓名: C(CNO,CNAME,CTEACHER) 代表 课号,课程名称.授课老师 SC(SNO,CNO,SCGRADE) 代表 学号.课号.课程成 ...
- quick-cocos2d-x游戏开发【1】——引擎结构总览和创建项目
好吧,我还是忍不住想写点关于quick的学习笔记,确实网上关于它的教程太少太少了,简单把自己的所学所得分享一下,有不正确之处还请拍砖. 首先下载引擎包.触控收购quick之后.如今cocos中文站的主 ...
- Python 不同对象比較大小
万恶的源泉: Fireboo的疑问(当然 lambda 本身写的就有问题): >>> filter( lambda x: x > 2, [ 1, [ 1, 2, 3 ], 2, ...
- kafka 生产者消费者 api接口
生产者 import java.util.Properties; import kafka.javaapi.producer.Producer; import kafka.producer.Keyed ...
- 洛谷 P2822 [ NOIP 2017 ] 组合数问题 —— 数学
题目:https://www.luogu.org/problemnew/show/P2822 阶乘太大,算不了: 但 k 只有 8 个质因子嘛,暴力60分: #include<iostream& ...
- 21. Ext中表格自适应高度
转自:https://blog.csdn.net/happy492/article/details/6401099 1. 下面的代码中width和height的初始值为tab的开始大小,当浏览器窗口变 ...
- redis之简单动态字符串(SDS)
O(N):时间复杂度 N的值越大 时间复杂度随N的平方增大 O(1):就是说N很大的时候,复杂度基本不增长了.基本就是常量了. 在Redis数据库里 包含字符串值的键值对 在底层都是由SDS实现的. ...
- 洛谷 P1045 麦森数
题目描述 形如2^{P}-1的素数称为麦森数,这时P一定也是个素数.但反过来不一定,即如果P是个素数,2^{P}-1不一定也是素数.到1998年底,人们已找到了37个麦森数.最大的一个是P=30213 ...
- printf的整型
参 数 说 明 %d 输出数字长为变量数值的实际长度 %md 输出m位(不足补空格,大于m位时按实际长度输出) %-md m含义同上.左对齐输出 %ld l(小写字母)表示输出“长整型”数据 %m1 ...
- 2017杭电多校第七场1005Euler theorem
Euler theorem Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others) ...