01_Hibernate概述作者: 风离紫竹--tryzq521@126.com |
1.Hibernate是一款工作在持久化层的大型、开源、免费的ORM框架。
2.持久化
①广义:将数据从不可掉电的设备保存到可以掉电的设备上。
②狭义:指的是所有和数据库相关的操作
[1]保存:INSERT INTO
[2]删除:DELETE
[3]更新:UPDATE
[4]查询:SELECT——在Hibernate中特指使用HQL或QBC等专门的查询API查询数据
[5]加载:根据一个对象的OID从数据库表中加载一个对象到内存
*OID:Object ID 一个对象中与数据库表的主键对应的属性
3.ORM
①概念:Object Relationship Mapping 对象关系映射
[1]对象:Java对象
[2]关系:关系型数据库
②JDBC中实现的ORM
Java概念 | 数据库概念 |
类 | 表 |
对象 | 记录/行 |
属性 | 字段/列 |
③Hibernate能够实现的ORM
[1]单个类→单个表
[2]单向多对一
[3]双向多对一
[4]基于外键的一对一
[5]基于主键的一对一
[6]单向多对多
[7]双向多对多
……
4.Hibernate特点
①简化持久化层开发:很少需要编写SQL语句
session.save(student); |
②将对数据库的操作转化为对Java对象的操作
Student student = (Student)session.get(Stduent.class,1);
student.setAge(30);
|
UPDATE STUDENTS SET AGE=30 WHERE STUDENT_ID=1 |
③将类与类之间的关联关系映射为表与表之间的关系
④Hibernate分别在线程和进程的级别上维护了两个级别的缓存
[1]一级缓存:线程级别的,由Session对象维护
[2]二级缓存:进程级别的,由SessionFactory对象维护
⑤屏蔽不同数据库平台之间的差异
不同数据库之间在执行SQL语句方面存在差异
MySQL分页:LIMIT子句
Oracle分页:ROW_NUM
jQuery
……
5.安装Hibernate插件
6.搭建Hibernate运行环境
①加入Hibernate的JAR包
Hibernate\lib\hibernate-release-4.2.4.Final\lib\required目录下全部
②创建Hibernate自身的配置文件,并加入必要的几个设置
[1]使用插件创建Hibernate Configuration File文件
[2]保持默认文件名:hibernate.cfg.xml
[3]加入配置信息
-
-
- 连接数据库的用户名
- 连接数据库的密码
- 连接数据库的URL地址
- 连接数据库的驱动类全类名
- 是否在控制台打印Hibernate自动生成的SQL语句
-
- 在学习和测试以及开发阶段:true
- 在项目上线运行后:false
- 是否将控制台打印的SQL语句进行格式化:true
- 自动生成数据库表的策略:update
- 指定数据库方言:告诉Hibernate我们当前使用的数据库是什么
- 指定hbm映射文件的位置
-
③加入MySQL驱动
7.HelloWorld
①目标:将Student对象通过Hibernate保存到数据库中
②步骤
[1]创建Student类
[2]创建能够将Student类映射为数据库表的hbm文件
[3]在Hibernate总配置文件(cfg文件)中指定hbm文件的位置
[4]在junit单元测试中编写代码
02_Session作者: 风离紫竹--tryzq521@126.com |
1.概述
①Session接口:代表Hibernate应用程序和数据库之间的一次会话。一次会话会包含很多次的数据库操作。
②作用
[1]执行基本的增删改查操作
[2]维护了Hibernate的一级缓存
2.Session缓存
①Session缓存能够给我们带来什么样的好处?
[1]减少访问数据库的次数,提升程序性能。
[2]可以通过操作加载到缓存中的对象,修改数据库中对应的数据,简化开发
②Session缓存的操作[理论:理解]
[1]flush:推送 将Session缓存中数据的改变推送到数据库
-
-
- 提交事务之前
- 手动调用 flush() 方法
- 执行 HQL 查询前
-
[2]refresh:刷新 将数据库中的数据的改变提取到缓存中
[3]clear:清空Session缓存
③脏检查机制
Session将数据库中的记录封装为对象加载到缓存中,会为这个对象建立一个镜像,这个镜像就可以作为缓存中数据是否有修改的一个参照。Session在执行flush操作时,会将book对象和镜像进行比较,如果发现属性值不一致,则说这个book对象是“脏”的。
在检测到“脏”的数据时,flush操作会通过发送UPDATE等SQL语句的形式,将数据的修改推送到数据库中。
3.Session缓存相关的对象状态
①四种状态
[1]临时状态
(1)OID是否为null:为null
(2)在不在Session缓存中:不在
(3)数据库中是否有对应的记录:没有
Book book = new Book(null,"xxx");
session.save(book);
|
[2]持久化状态
(1)OID是否为null:不为null
(2)在不在Session缓存中:在
(3)数据库中是否有对应的记录:有
Book book = (Book)session.get(Book.class,1); |
[3]游离状态
(1)OID是否为null:不为null
(2)在不在Session缓存中:不在
(3)数据库中是否有对应的记录:有
Book book = new Book(1,"WWW");
session.update(book);
|
[4]删除状态
(1)OID是否为null:不为null
(2)在不在Session缓存中:不在
(3)数据库中是否有对应的记录:没有
session.delete(book); |
03_单表映射作者: 风离紫竹--tryzq521@126.com |
1.将一个Java类映射为一个数据库表
2.主键生成方式:native
3.映射日期类型:java.util.Date
4.映射大对象
5.派生属性
①含义
一个可以通过其他属性计算得到值的属性
订单中的产品数量×单价=订单总金额
②配置方式
<
property
name
=
"amount"
type
=
"int"
formula
=
"(SELECT COUNT*PRICE FROM ORDERS WHERE ORDER_ID=ORDERS.ORDER_ID)"
/>
|
不在数据库表中生成对应的列
③生成的SQL语句
select
order0_.ORDER_ID as ORDER_ID1_7_0_,
order0_.COUNT as COUNT2_7_0_,
order0_.PRICE as PRICE3_7_0_,
(SELECT
order0_.COUNT*order0_.PRICE
FROM
ORDERS
WHERE
order0_.ORDER_ID=ORDERS.ORDER_ID) as formula0_0_
from
ORDERS order0_
where
order0_.ORDER_ID=?
|
04_单向多对一作者: 风离紫竹--tryzq521@126.com |
1.单向多对一概念
①多对一:Customer[1]和Order[多]
一个顾客可以生成很多订单,但一个订单只能属于一个顾客
②单向:
从Order对象可以通过调用getCustomer()方法获取到Customer对象
Customer对象中没有获取Order集合的getOrderSet()这样的方法
2.创建持久化类
①Customer就是一个普通的持久化类
②Order中需要有Customer对象的引用,并提供getXxx()、setXxx()方法
3.编写hbm映射文件
①Customer端:普通的单表映射
②Order端:将Customer属性映射为外键
<!-- 映射Order类的customer属性 -->
<!-- many-to-one标签:映射多对一关联关系 -->
<!-- name属性:指定根据当前Order类中的哪个属性来建立关联关系 -->
<!-- class属性:指定对方全类名 -->
<!-- column属性:指定在ORDERS表中关联CUSTOMERS表的外键列的列名 -->
<
many-to-one
name
=
"customer"
class
=
"com.atguigu.mapping.entity.Customer"
column
=
"CUSTOMER_FK"
/>
|
4.CRUD操作
①保存顺序如果是先保存有外键的Order对象,那么将产生额外的UPDATE语句,导致性能下降。
原因:先保存Order时,Customer还没有被保存,那么也就没有自增的主键,Order的外键列也就没有值,所以需要在保存Customer生成主键后,再回过头来去设置Order的外键
②查询Order对象时,默认是采取延迟加载策略,并没有查询关联的Customer对象。
-
- 设置lazy=false,则采取立即检索策略,在查询Order后马上再发送一条SQL语句查询关联的Customer
- 设置fetch=join,以迫切左外连接的方式在查询Order对象本身的时候就将关联的Customer对象也查询出来了
- 凡是延迟加载的地方都会因为关闭Session而抛出懒加载初始化异常
③通过Order对象可以修改关联的Customer对象的属性
④如果一个Customer对象被其他Order对象关联,那么Customer对象不能直接删除,否则会违反外键约束
05_双向多对一作者: 风离紫竹--tryzq521@126.com |
1.概念
①从Order对象能够获取到关联的Customer对象
②从Customer对象也能够获取到关联的Order对象的集合
2.持久化类
①Order还是和之前单向多对一的时候一样:维护一个Customer对象的引用
②Customer需要增加一个Order对象的集合:Set
3.映射方式
①Order端还是和单向的时候完全一样
<
many-to-one
name
=
"customer"
class
=
"com.atguigu.mapping.entity.Customer"
column
=
"CUSTOMER_FK"
/>
|
②Customer端使用set标签来映射orderSet属性
<!-- 映射从一到多的关联关系 -->
<!-- name属性:指定当前Customer类中Order的集合属性 -->
<!-- table属性:ORDERS表的名字 -->
<
set
name
=
"orderSet"
table
=
"ORDERS"
>
<!-- 指定在ORDERS表中,关联Customer的外键列的列名 -->
<
key
column
=
"CUSTOMER_FK"
/>
<!-- Order类名 -->
<
one-to-many
class
=
"Order"
/>
</
set
>
|
4.关于inverse属性
①如果没有设置inverse=true,那么保存Customer和Order时,即使先保存Customer,也仍然会多出来额外的UPDATE语句。
②原因:在双向关联关系中,Customer这一端也要维护关联关系。多出来的UPDATE语句就是Customer端维护关联关系的体现。
③维护关联关系:确保自己能够找到对方。
④inverse属性:设置为true,表示反转维护关联关系的方向,自己这一端放弃,由对方负责维护
06_基于外键的一对一作者: 风离紫竹--tryzq521@126.com |
1.实现的基本思路:在多对一的基础上,在多的一端给外键列加唯一约束。
2.持久化类:彼此有对方的引用
3.映射方式
①Husband端
<!-- 映射1-1关联关系 -->
<!-- property-ref属性:告诉Hibernate ,Husband关联Wife时,和Wife类中的哪个属性对应 -->
<
one-to-one
name
=
"wife"
class
=
"Wife"
property-ref="husband"
/>
|
②Wife端
<!-- 在多对一的基础上添加唯一约束,实现一对一 unique="true" -->
<
many-to-one
name
=
"husband"
class
=
"Husband"
unique= "true"
column
=
"HUSBAND_FK"
/>
|
07_基于主键的一对一作者: 风离紫竹--tryzq521@126.com |
1.持久化类和基于外键的完全一样
2.映射方式
①一对一的其中一方的主键生成方式使用foreign,以一个外键作为主键。其实质是以另一个表的主键值作为自己的主键值。主键值相同的两条记录之间是一对一的关联关系。
②Wife端配置
<
class
name
=
"Wife"
table
=
"WIFES"
>
<
id
name
=
"wifeId"
type
=
"java.lang.Integer"
>
<
column
name
=
"WIFE_ID"
/>
<!-- 以husband属性的OID的值作为自己的主键 -->
<
generator
class
=
"foreign"
>
<
param
name
=
"property"
>
husband
</
param
>
</
generator
>
</
id
>
<
property
name
=
"wifeName"
type
=
"java.lang.String"
>
<
column
name
=
"WIFENAME"
/>
</
property
>
<!-- 添加constrained="true",给主键列添加外键约束 -->
<
one-to-one
name
=
"husband"
class
=
"Husband"
constrained
=
"true"
/>
</
class
>
|
③Husband端配置
<
one-to-one
name
=
"wife"
class
=
"Wife"
/>
|
3.保存顺序:无论先保存的是自己生成主键的一端,还是先保存以外键作为主键的一端,生成的SQL语句都是自己生成主键的一端的insert先执行,因为主键是不能暂时置空的,必须有值以后才能保存。
08_单向多对多作者: 风离紫竹--tryzq521@126.com |
1.持久化类
Teacher:没有Student集合的引用。
Student:有Teacher的集合的引用。
2.映射多对多关联关系关键:需要中间表
注:中间表以两个外键列结合在一起构成一个“联合主键”。联合主键:用在数据库表中无法使用某一个单一字段作为主键时,使用多个字段组合起来的值作为主键,此时参与联合主键的某一列的值是可以重复的,但结合在一起后就不能重复了。
3.映射方式
①Teacher端:普通的单表映射
②Student端
<!-- 映射多对多关联关系 -->
<!-- table属性:中间表的表名!!! -->
<
set
name
=
"teacherSet"
table
=
"INNER_TABLE"
>
<!-- 负责制定自己这一端在中间表中的外键列的列名 -->
<
key
column
=
"STUDENT_FK"
/>
<!-- 对方在中间表中的外键列的列名 -->
<
many-to-many
class
=
"Teacher"
column
=
"TEACHER_FK"
/>
</
set
>
|
保存时生成的SQL语句与保存顺序无关
09_双向多对多作者: 风离紫竹--tryzq521@126.com |
1.关键:必须有其中某一方放弃维护关联关系
2.持久化类:彼此都有对方的集合的引用
Teacher:Student集合
Student:Teacher集合
3.配置方式
还需要其中某一方放弃维护关联关系:inverse = "true"
原因:如果不这样做,则两边都维护关联关系,那么其中一端维护关联关系会插入9条记录,另一端再维护关联关系会继续再插入同样的9条记录,但刚插入第一条的时候就提示主键重复导致插入失败。
10_检索策略作者: 风离紫竹--tryzq521@126.com |
1.基本理念
使用更好的检索策略,目的是为了提升程序的性能。
①不让不需要的数据进入内存。Customer关联的Order集合,如果不需要使用,那么就不加载到内存中,节约内存空间。
②尽量减少访问数据库的次数,一方面是通过缓存,让需要使用的数据只加载一次,另外通过必要的设置让查询使用尽可能少的SQL语句。
2.类级别的检索策略:Session对象的load()方法
①load()方法默认行为:只加载指定类型的代理对象,只有OID有值,其他属性在用到的时候才查询数据库得到。
②将load()方法设置为立即加载:找到要加载的类型对应的hbm文件,设置class标签的lazy属性为false
<
class
name
=
"Customer"
table
=
"CUSTOMERS"
lazy
=
"false"
>
……
</class>
|
Tips:Hibernate中Session的get()方法和load()方法的区别?
get()方法是立即加载,load()方法是延迟加载。
3.从Customer加载Order集合:针对hbm文件中的set标签进行设置
①lazy属性
[1]默认值true:延迟加载关联的Order集合
[2]false:立即加载关联的Order集合,此时一共是两条SQL语句
[3]extra:增强的延迟加载。如果需要的仅仅是集合的相关属性,例如:集合长度等,仅仅发送类似于count()这样的聚合函数语句来查询,具体Order对象的属性等用到的时候再查询。
②batch-size属性:对于加载到内存的多个Customer对象,会有很多关联的Order集合需要初始化,这个属性指定每次初始化Order集合的数量
③fetch属性
[1]subselect:对于加载到内存的多个Customer对象,会有很多关联的Order集合需要初始化,此时就会以子查询的形式,将关联的Order集合全部查询进来,忽略batch-size属性的设置。
[2]join:作用是在查询单个Customer对象时,以“迫切左外连接”的方式查询关联的Order集合,但是对于查询Customer集合无效
4.从Order对象加载Customer对象:many-to-one标签
①lazy属性
[1]默认值:proxy
[2]false:会在查询Order对象后,再另外发送SQL语句立即加载关联的Customer
②fetch属性:取值为join时,会以迫切左外连接的方式通过1条SQL语句查询关联的Customer对象
③batch-size:当前查询多个Order对象时,可以根据Customer的hbm文件中的class标签的batch-size属性决定一次初始化Customer对象的个数
5.在双向关联中Order会受到Customer的影响
①如果Customer的class标签设置lazy=false,那么加载Order对象的时候,会使用迫切左外连接查询关联的Customer,即使没有为Order设置fetch=join
②通过Order延迟加载Customer的时候,如果Customer的set标签设置了fetch=join,那么也会将Customer关联的Order集合查询得到
11_检索方式作者: 风离紫竹--tryzq521@126.com |
1.概述
①根据OID加载对象
session.get(Customer.class,1);
②根据对象图进行导航
Customer c = (Customer)session.get(Customer.class,1);
c.getOrderSet().size();
③HQL:Hibernate Query Language
④QBC:Query By Criteria
⑤本地SQL:执行原生的SQL语句
2.HQL
①面向对象的查询语言,语法结构和SQL非常相似,但SQL是针对数据库表和数据库表的列进行查询,HQL是针对Java类和Java类的属性进行查询。
②HQL查询相关的API支持连缀调用方法
③HQL支持投影查询、报表查询、分页查询、排序、分组、子查询等等。
④支持动态绑定参数
12_二级缓存作者: 风离紫竹--tryzq521@126.com |
1.概述
①★二级缓存中适合保存的数据
[1]经常被读取,很少或根本不会被修改的数据
[2]不是特别重要,允许出现偶尔的并发问题
例如:商城项目中商品的分类;三级联动中的地址数据……
②★二级缓存中不适合存放的数据
[1]经常被修改的数据,放入二级缓存会经常发生并发问题
[2]重要的数据,不允许出现任何的并发问题,例如:财务数据
[3]与其他应用程序共享的数据,会导致二级缓存中的数据在不知情的情况下被其他程序修改
③Hibernate中的二级缓存[了解]
[1]内置:保存Hibernate的配置信息,外部不能操作。
[2]外置:用于保存应用程序数据的缓存。Hibernate默认并没有提供,需要加入第三方二级缓存产品并进行配置才能够使用。
④二级缓存的并发策略[了解]
由于二级缓存中的数据在最开始的时候也是从数据库中取出来的,那么二级缓存数据在最初的时候从数据库中读取数据时也要考虑并发问题,体现为并发访问策略。
并发访问策略 | 数据库事务隔离级别 |
非严格读写 | 读未提交 |
严格读写 | 读已提交 |
事务型 | 可重复读 |
只读 | 串行化 |
2.使用EHCache二级缓存
①导入EHCache的jar包
hibernate-release-4.2.4.Final\lib\optional\ehcache目录下全部
②导入EHCache自身的配置文件
hibernate-release-4.2.4.Final\project\etc\ehcache.xml文件
复制到src目录下,保持默认设置即可
③在Hibernate中开启二级缓存功能
<
property
name
=
"hibernate.cache.use_second_level_cache"
>
true
</
property
>
|
④在Hibernate中指定二级缓存产品
参照:hibernate.properties,找到org.hibernate.cache.
internal.EhCacheRegionFactory
但是这个值不对,应该改为:org.hibernate.cache.
ehcache.EhCacheRegionFactory
3.针对某个持久化类开启二级缓存功能
<!-- 针对Department类开启二级缓存功能 -->
<!-- usage属性:设置二级缓存的并发访问策略 -->
<!-- class属性:指定目标类型全类名 -->
<
class-cache
usage
=
"read-write"
class
=
"com.atguigu.mapping.bean.Department"
/>
|
4.集合级别的二级缓存
①虽然Department类的对象经过设置后放入了二级缓存,但管理的Employee的集合并没有享受到这个待遇。
②需要另外开启集合的二级缓存。但此时的配置并不是针对某个集合类型,而是针对某个类中的集合属性。
<!-- 针对Department对象的empSet属性开启二级缓存功能 -->
<
collection-cache
usage
=
"read-write"
collection
=
"com.atguigu.mapping.bean.Department.empSet"
/>
|
如果仅仅开始集合属性本身二级缓存功能,那么Hibernate仅仅在二级缓存的集合属性中,保存每个对象的OID的值,在需要用到每个对象时,再根据OID的值去查询数据库,反而增加了SQL语句。
③为了将集合属性中的每个对象真正的保存到二级缓存中,还需要针对集合元素类型开启类级别的二级缓存,在我们现在的例子中,需要对Employee类进行配置。
<
class-cache
usage
=
"read-write"
class
=
"com.atguigu.mapping.bean.Employee"
/>
|
5.查询缓存
①开启功能
<
property
name
=
"cache.use_query_cache"
>
true
</
property
>
|
②让Query对象设置为考虑二级缓存的存在
调用setCacheable(true)方法
|
6.iterator()方法
①只查询目标数据库表的主键值,也就是持久化类的OID属性值
②在需要用到具体的非OID的属性值的时候,根据OID的值,到二级缓存中查找,此时如果二级缓存中有需要的数据,那么效率会很高。
13_在HibernateDao中获取Session对象作者: 风离紫竹--tryzq521@126.com |
1.为了保证多个Dao方法能够使用同一个事务,需要让它们能够得到同一个Session对象。
2.基本思想:将Session对象绑定到当前线程上
14_Hibernate总结作者: 风离紫竹--tryzq521@126.com |
1.
静:
配置
①Hibernate全局配置
[1]连接数据库的基本信息
[2]Hibernate运行参数
②hbm映射文件配置
[1]单表映射:主键生成方式
-
-
- native
- foreign
-
[2]
关联关系映射
-
-
- 单向多对一
- 双向多对一
- 基于外键的一对一
- 基于主键是一对一
- 单向多对多
- 双向多对多
-
2.
动:
执行
①Session一级缓存
[1]目的:减少访问数据库的次数,提升程序性能。
[2]缓存操作:参照事务隔离级别
-
-
- flush操作:将缓存中数据的修改推送到数据库中
- refresh操作:将数据库中数据的修改提取到缓存中,刷新缓存
-
[3]
持久化对象的四个状态
-
-
- 临时状态
- 持久化状态
- 游离状态
- 删除状态
-
[4]
Session核心方法
-
-
- 保存对象:save()
- 根据OID加载对象:
-
- get():默认立即加载
- load():默认延迟加载
- 更新对象:update()
- 删除对象:delete()
- 保存或更新对象:saveOrUpdate()
-
②SessionFactory二级缓存
[1]二级缓存的作用
[2]两个级别的缓存和线程、进程的关系
[3]二级缓存中适合保存的数据和不适合保存的数据
③检索策略
[1]延迟加载
类级别:session.load(Customer.class,1);
关联的集合对象:customer.getOrderSet();
关联的单个对象:order.getCustomer();
[2]立即加载
类级别:session.get(Customer.class,1);
关联的集合对象:set标签的lazy属性设置为false
关联的单个对象:many-to-one标签的lazy属性设置为false
[3]迫切左外连接
关联的集合对象:set标签的fetch属性设置为join
关联的单个对象:many-to-one标签的fetch属性设置为join
[4]batch-size:当内存中存在多个需要被初始化的Customer或Order集合时,指定每一次初始化的个数。
Customer初始化个数:在Customer的hbm文件的class标签内设置
Order集合的初始化个数:在Customer的hbm文件的set标签内设置
*当set标签的fetch属性设置为subselect时,batch-size属性会被忽略掉
④检索方式
[1]根据OID加载一个对象
[2]针对持久化状态的对象根据对象图进行导航
[3]
HQL
[4]
QBC
[5]执行本地SQL
⑤HibernateDao
让每一个dao方法都从当前线程上获取Session对象,以保证它们可以使用同一个事务
factory.
getCurrentSession();