Hibernate Framework
简介:
Hibernate框架是一个全自动的ORM框架,它拥有强大的映射功能,提供了缓存机制、事务管理、拦截器机制、查询语句的多方面支持
本系列笔记可能会遇到的专业词汇有:
Framework, 框架,某一类问题的总体解决方案
ORM,
Object Relationship Mapping
, 对象关系映射DATABASE, 数据库,存储数据的一种方式
HQL,
Hibernate Query Language
, Hibernate查询语句Transaction, 事务,一组相关的操作
Session, 会话
本系列笔记包含如下的课程内容:
- Hibernate框架原理和开发流程
- 框架缓存机制
- 对象关系映射
- 框架提供的查询机制
- 基于底层的
SQL
查询机制 - 基于
HQL
查询机制 - 基于
Criteria
查询机制
- 基于底层的
Hibernate 关联映射
此章节重点讲解Hibernate框架的关联映射
章节重点:
- 实体关系
- 一对多、多对多、一对一关联
- 加载策略
- 级联操作
章节难点:
- 多对多关联映射
- 延迟加载策略
实体关系
实体关系(Entity Relationship)是用来描述问题领域中实体之间关系的一种表述。在面向对象分析 (OOA)阶段,通常用E-R图来表达实体类之间的关系;然后再在面向对象设计(OOD)阶段,将E-R使用具体语言比如Java、C#等加以实现。
实体之间的关系有:
- 关联关系 [横向关系]
- 一对多关联
- 一对一关联
- 多对多关联
- 依赖关系 [横向关系]
- 继承关系 [纵向关系]
- 实现关系 [纵向关系]
注:所有的关联关系都有 单向关联和双向关联 两种
注:在Hibernate中,支持关联关系映射和继承关系映射,而依赖可以改写为关联,实现本质上就是继承。
本章节重点讲解关联关系映射,在附录部份我们讲解一下继承关系映射
一对多关联
一对多关联关系是最常见的一种关系,描述为:
类型A的一个对象可以持有类型B的多个对象的引用,同时类型B的任何一个对象最多只能持有A的一个对象
典型的一对多例子:
客户:订单,一个客户可以有多个订单,每个订单只对应一个客户;
部门:员工,一个部门可以有多个员工,每个员工只从属一个部门;
省份:城市,一个省份有多个城市,每个城市只属于一个省份。
在关系型数据库中,通常的做法是:
Many的一方设置FK外键,引用One的主键,作为外键约束;
如果只是一方持有另一方的引用,叫做
单向关联(unidirectional)
单向关联图示
如果双方同时持有对方的引用,叫做
双向关联(Bidirectional)
双向关联图示
代码示例 使用Annotation来描述实体关联映射
部门类
1 |
|
员工类
1 |
|
上面的代码描述的是双向的一对多关联, 需要注意的是:在面向关系的描述中,不管是双向或是单向,在映射到关系型数据库中都是同一种结果,如下图
注: @Column和@JoinColumn 的区别
前者定义实体类的属性,映射到数据库中某一张表的具体字段类型(数字,字符串,日期等),后者定义连接(Join)两个实体类之间的关系,映射到数据库中,就是两张表之间的FK外键。
注意,@JoinColumn 写在哪个实体类中,不代表FK就在这个类映射的表中。 可以查看[API]看案例
思考?
单向关联 or 双向关联?
答:没有固定的配置方式,一切可以按自己的需求进行。
可以通过下图的对比来进行选择
一对一关联
类型A到类型B的一对一关系描述为:
类型A的任何一个对象最多持有类型B的一个对象的引用,
类型B的任何一个对象最多持有类型A的一个对象的引用。
典型的一对一例子:
丈夫: 妻子,一个丈夫对应一个妻子;
班级:班长,一个班级有一个班长;
用户:身份证号,一个客户持有一个身份证号。
注:一对一关系,在关系型数据库中,可以视作一对多关系的一种特例。
表A和表B是一对一关系,实现方式:
- A表和B表,各自有独立的主键,B表设置 FK 外键字段,引用A表的主键,并增加unique唯一约束。
- A表有独立主键,B表无独立主键,B表的主键是引用A表的外键。[
主外键合一
]
一对一的单向关联和双向关联,都只有一种配置方法, 如下图:
代码示例 使用Annotation来描述实体关联映射
Husband类
1 |
|
Wife类
1 |
|
可以看出,一对一的关联就是一种特殊的一对多,因为在表结构看来,就是在外键的基础上再多一个唯一性约束
多对多关联
类型A到类型B的多对多关系描述为:
类型A的任何一个对象可以持有类型B的多个对象的引用,
类型B的任何一个对象可以持有类型A的多个对象的引用。
典型的一对一例子:
学生:课程,一个学生修多门课,一门课有多个学生修;
演员:角色,一个演员可以演多个角色,一个角色可由不同演员演绎;
用户:角色,一个用户可以分配多个角色,一个角色可以分配给多个用户。
注:
在关系型数据库中,体现多对多关系,必须要配置 中间表该表只具有2个FK字段,分别引用到两个表中的ID主键. 如下图
多对多的单向关联和双向关联,都只有一种配置方法,如下图:
多对多代码示例 使用Annotation来描述实体关联映射
Role类
1 |
|
User类
1 |
|
注,多对多关系在映射时无论在哪边来描述关系都是可以的。
相关的注解
- [@OneToMany]
- [@ManyToOne]
- [@OneToOne]
- [@ManyToMany]
- [@JoinColumn]
- [@JoinTable]
操作原则
我们通过代码操作关联关系时,应该遵守如下三个原则:
- 原则一
- 在关系型数据库中,如需体现记录和记录之间的对应关系,则需在OO层面也体现对象和对象之间的对应关系。
- 原则二
- 如果是双向关联,类型A与类型B互相持有对象的引用,则使用子表对象set主表对象。
- 原则三
- 从无到有,先主后子;
- 从有到无,先子后主。
加载策略
在Hibernate框架中,加载数据有两种策略,如下:
- 懒汉模式策略, 也叫 Lazy模式
- 不确定资源是否被程序调用,为了避免资源浪费,等需要使用时,再去初始化资源;
- 饿汉模式策略, 也叫 Eager模式
- 为了让使用者不要等待,一开始就初始化资源。尽量保证被初始化的 资源会被使用到.
Hibernate框架根据不同的情况,自动选择采用哪种策略加载数据,当然,也可以手动强制配置
Hibernate 中的懒加载机制:
对于实体类A的查询操作,如果A的属性中,存在实体类B的集合,默认对实体类B的集合使用延迟加载策略(懒汉模式)。可能发生懒加载的情况:一对多,多对多。
如果A的属性中,存在实体类C的单个对象,默认对实体类C的使用饿汉模式。注:
如果在使用多的一方的数据之前,session已被close,则会抛错:org.hibernate.LazyInitializationException: failed to lazily initialize a collection of xxx
以上是默认的情况,如果你在@OneToMany中,指定了 fetch=FetchType.EAGER, 则表示针对当前的实体对象加载时,会强制加载多的一边。
反过来,如果你在 @ManyToOne中,指定了 fetch=FetchType.LAZY, 则表示针对当前的实体对象加载时,对于
关联的一的一边,也将采用延迟加载
以上的两种做法,都是改变了默认的行为,除非你确定要这么做,否则,不建议修改默认行为。
级联操作
所谓级联操作,定义对某个实体类对象进行某种操作(CRUD)时,是否对其关联的对象也做类似操作。
目的:简化存在关联关系的实体类操作的开发效率。
- 级联操作是可选的,不是必须的。
- 在主表对象和子表对象,在某个或全部操作(CUD)上,生命周期都一致时,才使用级联。
- 级联操作,一般使用在OneToOne,OneToMany的操作上,ManyToMany通常不设置。
- 以上情况之外,完全不需要设置级联。
在@OneToOne,@OneToMany, @ManyToOne中,都可以通过属性cascade来指定级联操作类型。
Hibernate框架中定义了很多的级联操作类型,如:
假设存在如下的A类和B类关系:
1 | class A { |
则,定义在A类发生什么变化的时候,B类做出级联操作(指新增、更新、删除)
级联操作代码示例
Department类的代码片断
1 | ... |
没有指定级联删除的情况
1 | //获取部门 |
指定了级联删除的情况
1 | //而如果指定了级联删除的话,则可以直接删除 dept, 如下 |
常见问题
- 常见问题1
MappingException: Could not determine type for:
com.tz.hibernate.entity.Department, at table: emp, for columns: [org.hibernate.mapping.Column(dept)]原因:
属性类型是自定义实体类,不是基础数据类型,hibernate无法自动产生表的字段类型;
解决方案:
使用合适的Annotation,来表示当前类和该属性的类型, 之间的关联关系.
步骤:
先找到该实体类属性的get方法;加上合适的Annotation,比如: @ManyToOne, 如下图:
- 常见问题2
现象:
在配置双向关联的情况下,自动产生的表结构会发生异常(1:N,N:N 会冗余一张表,1:1紧耦合)。问题原因:
这种配置情况下, A和B是对等关系,hibernate无法得知哪个对象是主表对象。
解决方案:
通过配置,告知Hibernate,哪个对象是主表对象.
步骤:
- 先确定哪个是主表对象?
- 在一对多关系中,1的一方是主表,N的一方是子表.
- 在1:1,N:N关系中,由于是对等关系, 由开发者视情况而定.
- 进入主表对象源代码,找到之前配置的那个@关联关系, 新增mappedBy属性; 值就写get方法返回类型中,看哪个属性类型是主表对象.
- 值写的是属性名, 不是类型.
- 常见问题3
现象:
调用Session的save()方法,保存1:1, 1:N, N:N 的数据,能成功保存。但数据库中,FK外键没有值。问题原因:
没有遵守原则一:
要在关系型数据库中体现出记录之间的对应关系;
要在面向对象的层面,也体现出对象之间的对应关系.
解决方案:
保存对象之前,调用子表对象 set 主表对象,来指出对象之间的对应关系。
- 常见问题4
问题:
在一对多,或多对多的查询操作时,报错:org.hibernate.LazyInitializationException: failed to lazily initialize a collection of xxx原因:
在使用延迟加载的数据之前,session已被close。
解决方案:- 在session关闭之前,调用延迟加载的数据,完成加载; - 使用Query/Criteria接口,在查询级别上使用关联查询,避免延迟加载。
本章小节
- 掌握OneToMany,OneToOne,ManyToMany的配置和常用CRUD操作
- 了解延迟加载的原理和相关问题
- 了解级联操作的目的和要点
参考API
@JoinColumn
@OneToMany
@ManyToOne
@OneToOne
@ManyToMany
@JoinColumn
@JoinTable