“Hibernate笔记 4:核心知识”的版本间差异

来自Wikioe
跳到导航 跳到搜索
第117行: 第117行:


== Hibernate 缓存 ==
== Hibernate 缓存 ==
Hibernate 的缓存分为<span style="color: blue; font-size: 120%">“'''一级缓存'''”</span>和<span style="color: blue; font-size: 120%">“'''二级缓存'''”</span>,Hibernate 的这两级缓存都位于持久化层,存储的都是数据库数据的备份。
作用:减少对数据库的访问次数。


其中:
# “一级缓存”:Session 级别的缓存,事务范围。
#* Hibernate 的内置缓存(默认的缓存机制),不能被卸载;
# “二级缓存”:SessionFactory 级别的缓存,应用范围(应用中的所有 Session 共享)。
#* 默认关闭,需要手动配置;


=== 一级缓存(Session 缓存) ===
=== 一级缓存(Session 缓存) ===
  Session 中有一个缓存,被称为“Hibernate 的<span style="color: blue; font-size: 120%">'''一级缓存'''</span>”。
  Session 缓存:是一块内存空间,用来存放相互管理的对象。
作用:
1、减少访问数据库的频率。
2、保证缓存中的对象与数据库中的相关记录保持同步。【快照区】


Session 缓存是由一系列的 Java 集合构成的:
在使用 Hibernate 查询对象的时候,首先会使用对象属性的 <span style="color: blue">'''OID'''</span> 值在 Hibernate 的一级缓存中查找:
    当一个对象被加入到 Session 缓存中,这个对象的引用就加入到了 Java 的集合中,即使以后应用程序中的引用变量不再引用该对象,只要 Session 缓存不被清空,这个对象一直处于生命周期中。
# 如果找到匹配 OID 值的对象:就直接将该对象从“一级缓存”中取出使用,不会再查询数据库;
# 如果没有匹配 OID 值的对象:去数据库中查找相应数据,并将查询到的数据信息放置到“一级缓存”。
 
 
示例:
: <syntaxhighlight lang="Java" highlight="">
@Test
// 测试 Hibernate 一级缓存的存在
public void demo() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 查询客户1:发出 sql 查询,并将结果放入一级缓存
Customer customerl = session.get(Customer.class, 11);
System.out.println(customerl);
 
// 查询客户1:从一级缓存中获取数据,而不再发出 sql 查询
Customer customer2 = session.get(Customer.class, 11) ;
System.out.println(customer2);
 
// 验证两次查询结果:true。(一级缓存的内容是对象的地址)
System.out.println(customerl == customer2);
tx.commit();
session.close();
}
</syntaxhighlight>
: 从控制台输出(已配置 Hibernate 输出底层 sql 语句)可以发现,以上代码只发出了一次查询语句。


作用:
==== 内部结构(快照区) ====
# 减少访问数据库的频率。
Hibernate 向“一级缓存”放入数据时,同时复制一份数据放入到 Hibernate <span style="color: blue; font-size: 120%">“'''快照区'''”</span>中。
# 保证缓存中的对象与数据库中的相关记录保持同步。
    当使用 commit() 方法提交事务时,同时会清理 Session 缓存,这时会使用 '''OID''' 判断 Session 缓存中的对象和'''快照'''中的对象是否一致,
    1、'''如果两个对象中的属性发生变化,则执行 update 语句, 将缓存的内容同步到数据库,并更新快照'''。
    2、如果数据一致,则不执行 update 语句。
快照区的作用就是:<span style="color: blue">确保缓存的数据与数据库一致</span>。【持久态对象的“自动更新”,便依据于此】


==== 与实体状态 ====
==== 相关方法 ====
  Session 缓存,用于存放被当前工作单元加载的对象,这块缓存中有一个 '''map'''。
  Session 接口(及其他查询接口,如:Query)的相关方法会导致 Session 缓存的:增加、删除、清空。
1、在执行 '''save()''' 方法后,会生成一个 '''id'''(<span style="color: blue">'''OID'''</span>),这个 id 会保存在 map 的 '''key''' 中;
2、与之对应的 '''value''' 值就是该是该对象的引用,执行 '''commit()''' 方法后,数据库就有对应这条数据了,此时该实体处于“持久状态”;
3、当执行完 Session.'''close()'''后,处于“游离状态”。


区分实体的三种状态:
# '''增加''':
:{| class="wikitable"
## 调用 Session 接口的 '''save()'''、'''update()'''、'''saveOrUpdate()''' 时:如果 Session 缓存中没有相应的对象,Hibernate 就会把从数据库中查询到的相应对象信息加入缓存。
! 实体状态 !! OID !! Session缓存 !! 数据库
## 调用 Session 接口的 '''load()'''、'''get()''',及 Query接口的 '''list()'''、'''iterator()''' 时:如果 Session 缓存中有相应的对象,则直接取出并返回;否则,从数据库查询数据并放到缓存。
|-
# '''删除''':调用 Session 的 '''evict()''' 方法时,会从 Session 缓存移除对应数据。
| 瞬时状态 || null || 不存在 || 无记录
# '''清空''':调用 Session 的 '''clear()'''、'''close()''' 方法时,会清空 Session 缓存。
|-
| 持久状态 || 非null || 存在 || 有记录
|-
| 托管状态 || 非null || 不存在 || (可能)有记录
|}


==== 清理时机 ====
==== 清理时机 ====
Session 清理缓存的时机:
Session 缓存是由一系列的 Java 集合构成的:
    当一个对象被加入到 Session 缓存中,这个对象的引用就加入到了 Java 的集合中,即使以后应用程序中的引用变量不再引用该对象,只要 Session 缓存不被清空,这个对象一直处于生命周期中。
 
Session 缓存的清理时机:
# 提交前:当调用 Transaction 的 commit() 方法时,commit() 方法先清理缓存(前提是:FlushMode.COMMIT/AUTO),然后再向数据库提交事务。
# 提交前:当调用 Transaction 的 commit() 方法时,commit() 方法先清理缓存(前提是:FlushMode.COMMIT/AUTO),然后再向数据库提交事务。
# 查询前:当应用程序调用 Session 的 find() 或者 iterate() 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存,以保证查询结果能反映持久化对象的最新状态。
# 查询前:当应用程序调用 Session 的 find() 或者 iterate() 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存,以保证查询结果能反映持久化对象的最新状态。
# flush:当应用程序显示调用 Session 的 flush() 方法的时候。
# flush:当应用程序显示调用 Session 的 flush() 方法的时候。
: 如下图:
#* flush() 方法用于:强制更新缓存数据(而并非清空缓存),其过程:先清空缓存,再从数据库中读入对应缓存。
 
如下图:
:{| class="wikitable"
:{| class="wikitable"
! FlashMode !! Session.find()/iterate() !! Session.commit() !! Session.flush()
! FlushMode !! Session.find()/iterate() !! Session.commit() !! Session.flush()
|-
|-
| FlashMode.AUTO || ✔ || ✔ || ✔
| FlushMode.AUTO || ✔ || ✔ || ✔
|-
|-
| FlashMode.COMMIT || ✘ || ✔ || ✔
| FlushMode.COMMIT || ✘ || ✔ || ✔
|-
|-
| FlashMode.NEVER || ✘ || ✘ || ✔
| FlushMode.NEVER || ✘ || ✘ || ✔
|}
|}
:* 清理(✔),不清理(✘)
:* 清理(✔),不清理(✘)

2022年6月11日 (六) 00:50的版本


持久化类的编写规则

如果一个 Java 类与数据库表建立了映射关系,那么这个类称为是持久化类。 (该类有对应的映射文件与数据库表相关联)

编写规则:

  1. 持久化类衙要提供无参数构造方法
    因为在 Hibernate 的底层需要使用反射生成类的实例;
  2. 持久化类必须:属性私有,且提供公有的 get 和 set 方法
    因为在 Hibernate 底 层会将查询到的数据进行封装;
  3. 持久化类的属性要尽量使用包装类的类型。
    因为包装类和基本数据类型的默认值不同,包装类的类型语义描述更消晰而基本数据类型不容易描述;
  4. 持久化类要有一个唯一标识 OID 与数据库表的主键相对应。
    Hibernate 通过 OID 区分在内存中的持久化类;
  5. 持久化类尽量不要使用 final 进行修饰。
    因为 Hibernate 中有延迟加载的机制,这个机制中会产生【代理对象】,Hibernate 产生代理对象使用的是【字节码的增强技术】完成的,其实就是产生了当前类的一个子类对象来实现的。
    如果使用了 final 修饰待久化类,那么就不能产生子类,从而不能产生【代理对象】,那么 Hibernate 的【延迟加载】策略就会失效

什么是 OID?

OID(object identifier,“对象标识符”),是 hibernate 用于区分两个对象(持久化类)是否是同一个对象的标识。

持久化类的 OID 用于与数据库表的主键相映射,所以一般不手动指定。

OID 在对象持久化之前是 null,持久化的时候 hibernate 或者我们手动指定一个 id(被插入到数据库当做主键,在 session中 当做索引),所以,需要保证 OID 与主键的一致性,比如:类型、长度等。

主键生成策略

Hibernate 的主键依据:不同的主键类型、不同的数据库,可以有不同生成策略。

主键类型:

  1. 自然主键:带有业务含义的主键(比如:学号,工作编号)。
    • 如果不手动指定主键就提交缓存进行更新,则会报错!
    • 主键生成策略:
      1. assinged:Hibernate不维护主键,开发人员需要手动设置主键;
        • 如果不指定过元素的 <generator> 属性,则默认使用该主键生成策略。
  2. 代理主键:通过编码自动生成的,无业务含义的主键。
    • 主键生成策略:
      1. increment:(由 Hibernate 提供)自动增长
        • 适用主键:short、int、long 类型的主键;
        • 不适用与并发访问数据库;
      2. identity:(由数据库提供)自动增长
        • 适用主键:short、int、long 类型的主键;
        • 适用数据库:支持自动增长的数据库(如:DB2、 MySQL、 MS SQL Server、 Sybase 和 HypersomcSQL);(不适用于 Oracle)
      3. sequence:(由数据库提供)序列
        • 适用主键:short、int、long 类型的主键;
        • 适用数据库:支持序列的方式的数据库(如:Oracle、db2、sap、db、postgresql);
      4. native:本地策略,根据底层的数据库不同,自动选择使用 identity 还是 sequence。
        • 适用主键:short、int、long 类型的主键;
      5. uuid:Hibernate 采用 128 位的 UUID 算法来生成标识符。
        该算法能够在网络环境中生成唯一的字符串标识符,其 UUID 被编码为一个长度为 32 位的十六进制字符串
        • 适用于:字符串类型的主键。
        • 这种策略并不流行,因为字符串类型的主键比整数类型的主键占用更多的数据库空间。

生命周期(实体状态)

Session 的生命周期是以一个逻辑事物的开始和结束为边界,Session 的主要功能是提供创建、读取和删除映射的实体类的操作。

实体可能存在于三种状态:

  1. 瞬时态(transient):实体对象在内存是自由存在的(即为普通的 Java 对象),即:“该实体从未与任何持久化上下文关联过,它没有持久化标识”。
    • 特点:
      1. 不存在待久化标识 OID (相当于主键值);
      2. 尚未与 Session 关联;
      3. 数据库无对应记录;
    • 对象失去引用后将被 JVM 回收。
  2. 持久态(persistent):实体对象处于 Hibernate 框架所管理的状态,即:“该实体在数据库中有对应的记录,并拥有一个持久化标识”。
    • 特点:
      1. 存在待久化标识 OID
      2. 已与 Session 关联(加入到了 Session 缓存,且 Session 未关闭);
      3. 数据库有对应记录;
    • 持久态对象发生改变时,Hibernate 将会依据其改变自动更新。【无需 update 等操作】
      • 在一个 Session 中,对持久对象的改变不会马上对数据库进行变更,而是发生在 Transaction 终止,执行 commit 之后。
  3. 游离态(detached,脱管态):由持久态实体对象转变(关联的 Session 被关闭)而来。
    • 特点:
      1. 存在持久化标识 OID
      2. 失去了与 Session 的关联;
      3. 数据库有对应记录;
    • 游离态对象发生改变时,Hibernate 不能检测到。
      对象的引用依然有效,也可以继续被修改,但修改将不会影响到到数据库中的数据。
“瞬时态”与“游离态”的区别是“是否存在 OID”。(数据库对应记录不是重点)???

如:
   Customer customer = new Customer();   // 瞬时态
   customer.setCust_id(1);   // 脱管态
   customer.setCust_id(null);   // 瞬时态

状态转换

三种状态可以互相转换:

Hibernate 生命周期图.png

持久态的自动更新

Hibernate 会依据 persistent 状态的实体对象的属性变化,而自动更新(无需 update 等操作)数据库中相对应的记录。


注意:在一个 Session 中,对持久对象的改变不会马上对数据库进行变更,而是发生在 Transaction 终止,执行 commit 之后。

示例:

	@Test 
	// 测试:持久态对象的自动更新
	public void demo() { 
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransac七ion(); 
		
		// 获得持久态对象.
		Customer customer = session.get(Customer.class, 11);
		// 修改持久态对象
		customer.setCust name("主五"); 
		
		// session.update(custorner);   // 不用手动调用 update 方法就可以更新
		
		tx.commit(); 
		session.close(); 
	}
如上,更改持久态对象属性之后,不调用 update 等方法,依旧可以更新数据库记录。
持久态的自动更新,依赖于 Hibernate 的一级缓存。

Hibernate 缓存

Hibernate 的缓存分为一级缓存二级缓存,Hibernate 的这两级缓存都位于持久化层,存储的都是数据库数据的备份。

作用:减少对数据库的访问次数。

其中:

  1. “一级缓存”:Session 级别的缓存,事务范围。
    • Hibernate 的内置缓存(默认的缓存机制),不能被卸载;
  2. “二级缓存”:SessionFactory 级别的缓存,应用范围(应用中的所有 Session 共享)。
    • 默认关闭,需要手动配置;

一级缓存(Session 缓存)

Session 缓存:是一块内存空间,用来存放相互管理的对象。

作用:
1、减少访问数据库的频率。
2、保证缓存中的对象与数据库中的相关记录保持同步。【快照区】

在使用 Hibernate 查询对象的时候,首先会使用对象属性的 OID 值在 Hibernate 的一级缓存中查找:

  1. 如果找到匹配 OID 值的对象:就直接将该对象从“一级缓存”中取出使用,不会再查询数据库;
  2. 如果没有匹配 OID 值的对象:去数据库中查找相应数据,并将查询到的数据信息放置到“一级缓存”。


示例:

	@Test 
	// 测试 Hibernate 一级缓存的存在 
	public void demo() { 
		Session session = HibernateUtils.openSession(); 
		Transaction tx = session.beginTransaction(); 
	
		// 查询客户1:发出 sql 查询,并将结果放入一级缓存
		Customer customerl = session.get(Customer.class, 11);
		System.out.println(customerl); 

		// 查询客户1:从一级缓存中获取数据,而不再发出 sql 查询
		Customer customer2 = session.get(Customer.class, 11) ;
		System.out.println(customer2); 

		// 验证两次查询结果:true。(一级缓存的内容是对象的地址)
		System.out.println(customerl == customer2);
		
		tx.commit(); 
		session.close(); 
	}
从控制台输出(已配置 Hibernate 输出底层 sql 语句)可以发现,以上代码只发出了一次查询语句。

内部结构(快照区)

Hibernate 向“一级缓存”放入数据时,同时复制一份数据放入到 Hibernate 快照区中。

   当使用 commit() 方法提交事务时,同时会清理 Session 缓存,这时会使用 OID 判断 Session 缓存中的对象和快照中的对象是否一致,
   1、如果两个对象中的属性发生变化,则执行 update 语句, 将缓存的内容同步到数据库,并更新快照。
   2、如果数据一致,则不执行 update 语句。


快照区的作用就是:确保缓存的数据与数据库一致。【持久态对象的“自动更新”,便依据于此】

相关方法

Session 接口(及其他查询接口,如:Query)的相关方法会导致 Session 缓存的:增加、删除、清空。
  1. 增加
    1. 调用 Session 接口的 save()update()saveOrUpdate() 时:如果 Session 缓存中没有相应的对象,Hibernate 就会把从数据库中查询到的相应对象信息加入缓存。
    2. 调用 Session 接口的 load()get(),及 Query接口的 list()iterator() 时:如果 Session 缓存中有相应的对象,则直接取出并返回;否则,从数据库查询数据并放到缓存。
  2. 删除:调用 Session 的 evict() 方法时,会从 Session 缓存移除对应数据。
  3. 清空:调用 Session 的 clear()close() 方法时,会清空 Session 缓存。

清理时机

Session 缓存是由一系列的 Java 集合构成的:
    当一个对象被加入到 Session 缓存中,这个对象的引用就加入到了 Java 的集合中,即使以后应用程序中的引用变量不再引用该对象,只要 Session 缓存不被清空,这个对象一直处于生命周期中。

Session 缓存的清理时机:

  1. 提交前:当调用 Transaction 的 commit() 方法时,commit() 方法先清理缓存(前提是:FlushMode.COMMIT/AUTO),然后再向数据库提交事务。
  2. 查询前:当应用程序调用 Session 的 find() 或者 iterate() 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存,以保证查询结果能反映持久化对象的最新状态。
  3. flush:当应用程序显示调用 Session 的 flush() 方法的时候。
    • flush() 方法用于:强制更新缓存数据(而并非清空缓存),其过程:先清空缓存,再从数据库中读入对应缓存。

如下图:

FlushMode Session.find()/iterate() Session.commit() Session.flush()
FlushMode.AUTO
FlushMode.COMMIT
FlushMode.NEVER
  • 清理(✔),不清理(✘)

二级缓存(SessionFactory 缓存)

SessionFactory 在 Hibernate 中起到一个缓冲区作用,Hibernate 可以将自动生成的 SQL 语句、映射数据以及某些可重复利用的的数据放在这个缓冲区中。同时它还保存了对数据库配置的所有映射关系,维护了当前的二级缓存