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

来自Wikioe
跳到导航 跳到搜索
(创建页面,内容为“category:Hibernate == 关于 == Session 缓存:是一块内存空间,用来存放相互管理的对象。 作用: # 减少访问数据库的频率。【缓存查询数据】 # 保证缓存中的对象与数据库中的相关记录保持同步。【持久态自动更新】 === 缓存查询数据 === 在使用 Hibernate 查询对象的时候,首先会使用对象属性的 <span style="color: blue">'''OID'''</span> 值在 Hibernate 的一级缓存…”)
 
 
(未显示同一用户的5个中间版本)
第4行: 第4行:
  Session 缓存:是一块内存空间,用来存放相互管理的对象。
  Session 缓存:是一块内存空间,用来存放相互管理的对象。


=== 相关方法 ===
Session 接口(及其他查询接口,如:Query)的相关方法会导致 Session 缓存的:增加、删除、清空。
# '''增加''':
## 调用 Session 接口的 '''save()'''、'''update()'''、'''saveOrUpdate()''' 时:如果 Session 缓存中没有相应的对象,Hibernate 就会把从数据库中查询到的相应对象信息加入缓存。
## 调用 Session 接口的 '''load()'''、'''get()''',及 Query接口的 '''list()'''、'''iterator()''' 时:如果 Session 缓存中有相应的对象,则直接取出并返回;否则,从数据库查询数据并放到缓存。
# '''删除''':调用 Session 的 '''evict()''' 方法时,会从 Session 缓存移除对应数据。
# '''清空''':调用 Session 的 '''clear()'''、'''close()''' 方法时,会清空 Session 缓存。
== Session 缓存作用 ==
作用:
作用:
# 减少访问数据库的频率。【缓存查询数据】
# 减少访问数据库的频率。  ——【缓存查询数据】
# 保证缓存中的对象与数据库中的相关记录保持同步。【持久态自动更新】
# 保证缓存中的对象与数据库中的相关记录保持同步。  ——【持久态自动更新】


=== 缓存查询数据 ===
=== 缓存查询数据 ===
第66行: 第76行:


  <span style="color: Coral; font-size: 120%">持久态的自动更新,依赖于 Hibernate 一级缓存的“'''脏检查和缓存清理机制'''”。</span>
  <span style="color: Coral; font-size: 120%">持久态的自动更新,依赖于 Hibernate 一级缓存的“'''脏检查和缓存清理机制'''”。</span>
== 相关方法 ==
Session 接口(及其他查询接口,如:Query)的相关方法会导致 Session 缓存的:增加、删除、清空。
# '''增加''':
## 调用 Session 接口的 '''save()'''、'''update()'''、'''saveOrUpdate()''' 时:如果 Session 缓存中没有相应的对象,Hibernate 就会把从数据库中查询到的相应对象信息加入缓存。
## 调用 Session 接口的 '''load()'''、'''get()''',及 Query接口的 '''list()'''、'''iterator()''' 时:如果 Session 缓存中有相应的对象,则直接取出并返回;否则,从数据库查询数据并放到缓存。
# '''删除''':调用 Session 的 '''evict()''' 方法时,会从 Session 缓存移除对应数据。
# '''清空''':调用 Session 的 '''clear()'''、'''close()''' 方法时,会清空 Session 缓存。


== 脏检查机制(快照) ==
== 脏检查机制(快照) ==
第102行: 第103行:
  Hibernate 采用的是“数据版本比对”的方法来进行脏数据检查的。
  Hibernate 采用的是“数据版本比对”的方法来进行脏数据检查的。


== 缓存清理机制(缓存刷新机制) ==
== 缓存清理机制(缓存刷出) ==
  <span style="color: blue; font-size: 120%">'''缓存清理'''</span>:Session 在某些时间点,对当前持久化状态的缓存数据进行检查(<span style="color: blue">'''脏检查'''</span>),并且按照缓存中持久态对象的属性变化来'''同步更新数据库'''的过程。
  <span style="color: blue; font-size: 120%">'''缓存清理'''</span>:在某些时间点,Session 会对持久化状态的缓存数据进行检查(<span style="color: blue">'''脏检查'''</span>),并执行一些“必需的 SQL 语句”来把内存中的对象的状态同步到数据库中。
   
   
    ——【“缓存清理”并非“清空缓存”!!!可以理解为“(从缓存到数据库的)<span style="color: blue; font-size: 120%">'''缓存刷新'''</span>”】
这一过程也就是“<span style="color: blue; font-size: 120%">'''缓存刷出(flush)'''</span>”,其目的是“同步缓存数据到数据库”。  ——【注意:“缓存清理”并非“清空缓存”!!!】


=== 清理过程 ===
----
缓存清理过程中,Session 会对相关 SQL 语句进行合并优化,同时按照一定的顺序来执行 SQL 语句。
【SQL 语句与操作,在数据或顺序上都并非一一对应】


==== SQL 执行顺序 ====
清理过程中,涉及的 SQL 语句,必须按照正确的顺序发生,以保证功能的完整性。
注意:
1、上一顺序的所有操作(如,所有的“save()”)执行完成后,才会执行下一顺寻的操作(如,所有的“update()”)。
2、仅能保证其前后顺序,而不能确定何时执行这些语句(除非显示调用 flush())。


具体会按照下面的顺序发出执行:【???集合???】
# 所有:对'''实体'''进行'''插入'''的语句。
#* 其顺序按照对象执行 Session.save() 的时间顺序;
# 所有:对'''实体'''进行'''更新'''的语句;
# 所有:对集合进行删除的语句;
# 所有:对集合元素进行删除,更新或者插入的语句;
# 所有:对集合进行插入的语句;
# 所有:对'''实体'''进行'''删除'''的语句。
#* 其顺序按照对象执行 Session.delete() 的时间顺序;
即,ActionQueue 类的静态代码块中,EXECUTABLE_LISTS_MAP 对 ExecutableList 进行 put 的顺序。




如上,<span style="color: blue">对于实体对象,其顺序是:“'''insert'''”(save)->“'''update'''”->“'''delete'''”</span>。
'''例外情况''':
    如果对象使用 <span style="color: blue">'''native'''</span> 生成器来生成 '''OID''',那么当调用 Session 的 <span style="color: blue">'''save()'''</span> 方法保存该对象时,会立即执行向数据库插入该实体的 '''insert''' 语句。
'''原因''':
    1、在 save() 方法后,必须保证对象的 OID 是存在的;
    2、而 native 作为主键生成方式,必须在数据库执行 insert 语句后才能得到 '''OID'''。


==== SQL 合并优化 ====
Session 能够把几条'''相关的 SQL 语句合并为一条 SQL 语句''',以便减少访问数据库的次数,从而提高应用程序的数据访问性能。
合并一般是'''针对同一对象的同类动作'''。(如,对同一个持久化对象的多条 update,就可以优化成最终的一条 update)
即:
1、不同对象的操作不能合并。
2、不同操作一般不会一起优化。(如,save() 和 update())


=== 清理时机 ===
=== 清理时机(触发策略) ===
  当 Session 缓存中对象的属性发生了变化,Session '''并不会立即清理缓存和执行相关的 SQL update 语句''',而是在特定的时间点才清理缓存,这使得 Session 能够把几条'''相关的 SQL 语句合并为一条 SQL 语句''',以便减少访问数据库的次数,从而提高应用程序的数据访问性能。
----
  当 Session 缓存中对象的属性发生了变化,Session '''并不会立即清理缓存和执行相关的 SQL update 语句''',而是在特定的时间点才清理缓存。


==== 默认清理时机 ====
==== 默认清理时机 ====
默认情况下(FlushMode.AUTO),Session 缓存清理的时机:(触发策略)
默认情况:'''FlushMode.AUTO'''
# '''提交前''':当调用 Transaction 的 commit() 方法时,commit() 方法先清理缓存(前提是:FlushMode.COMMIT/AUTO),然后再向数据库提交事务。
 
#* 之所以在事务快结束时:一方面是因为可以减少访问数据库的频率,另一方面是因为可以尽可能缩短当前事务对数据库中相关资源的锁定时间;
默认情况下的清理时机:
#* commit() 会隐式调用 flush() 方法。(通过 SessionIml 的 flushBeforeTransactionCompletion() 实现隐式调用)
# '''提交前''':当调用 Transaction 的 '''commit()''' 方法时,会先清理缓存,然后再向数据库提交事务。
# '''查询前''':当应用程序调用 Session 的 find() 或者 iterate() 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存,以保证查询结果能反映持久化对象的最新状态。
#* commit() 会隐式调用 flush() 方法。
# '''flush''':当应用程序显示调用 Session 的 flush() 方法的时候。
#* 之所以在事务快结束时清理缓存:
#*# '''可以减少访问数据库的频率''';
#*# 可以尽可能'''缩短当前事务对数据库中相关资源的锁定时间''';
# '''查询前''':当应用程序调用 Session 的 '''list()''' 或者 '''iterate()''' 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存。
#* 以此保证查询结果能反映持久化对象的最新状态。
# '''flush''':当应用程序显示调用 Session 的 '''flush()''' 方法的时候。
#*【见:'''[[Hibernate笔记 4:核心知识#Flush机制]]'''】
#*【见:'''[[Hibernate笔记 4:核心知识#Flush机制]]'''】


如下图:
如下图:
:{| class="wikitable" style="text-align: center"
:{| class="wikitable" style="text-align: center"
! FlushMode !! Session.find()/iterate() !! Session.commit() !! Session.flush()
! FlushMode !! Session.list()/iterate() !! Session.commit() !! Session.flush()
|-
|-
| FlushMode.AUTO || ✔ || ✔ || ✔
| FlushMode.AUTO || ✔ || ✔ || ✔
第137行: 第184行:
:* 清理(✔),不清理(✘)
:* 清理(✔),不清理(✘)


==== 指定清理时机 ====
==== 设置清理时机 ====
可以通过 <span style="color: green; font-size: 120%">'''session.setFlushMode(FlushMode)'''</span> 来设置 Session 缓存清理时机。
【设置 FlushMode,应该在 session 开启事务之前】
 
FlushMode 的枚举值:
# “FlushMode.ALWAYS”:每次查询之前都“清理缓存”;
# “FlushMode.AUTO”:有时会在执行查询之前“清理缓存”,以确保查询永远不会返回过时状态。
#* 【默认】
# “FlushMode.COMMIT”:当 Transaction.commit() 被调用时“清理缓存”。
# “FlushMode.MANUAL”:仅在显示调用 Session.flush() 时才“清理缓存”;
#* 在性能优化时可能用:
#*: 比如 session 只做查询操作时,就不需要与数据库同步。
# “FlushMode.NEVER”:永不“清理缓存”。
#* 【已废弃。  ——由“FlushMode.MANUAL”代替】
 
== flush() ==
flush() 执行过程,就是“缓存清理”过程:
    由于 flush() 的特殊处理机制,可能会破坏事务提交的完整性,所以一般不建议使用此方法。
    但是在一些复杂的事务处理过程中,使用 flush() 可以规避一些不可预见的异常情况。
 
=== 应用场景一:解决同一事务的数据冲突(分隔 SQL) ===
在有多个 SQL 操作时,可能由于不同顺序等级(save、update、delete)的交叉执行,导致数据冲突。
此时,可以用 session.flush() 打断其原本的执行顺序,让它先干完一部分,来减少冲突。
 
'''示例''':(会产生“主键冲突”)
: ID 为主键
: <syntaxhighlight lang="Java" highlight="">
public void test_flush() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 创建 customer1 对象
Customer customerl = new Customer();
customerl.setID(“10001”);
customerl.setName(“张三”);
session.save(customerl);
// 更新 customer1 对象
customerl.setID(“10011”);
session.update(customerl);
// flush
//session.flush();
// 创建 customer2 对象
Customer customer2 = new Customer();
customer2.setID(“10001”);
customer2.setName(“李四”);
session.save(customer2);
tx.commit();
}
</syntaxhighlight>
'''分析''':
# 如上代码,会产生一次 flush 操作(commit 时);
# 由于 flush 相关的 SQL 顺序为:“save(customerl)”->“save(customer2)”->“update(customerl)”;
#*【所有 save 操作完成之后,再进行所有 update 操作,最后进行所有 delete 操作】
# 所以两次 save 的对象具有相同的主键(ID),出现冲突。
'''解决''':
: 如上,在“// flush”注释处,使用“session.flush()”,则将产生两次 flush:
# 第一次 flush,强制刷出 customerl 的数据到数据库,将完成“save(customerl)”和“update(customerl)”;
# 第二次 flush(commit 时),仅刷出 customer2 的数据到数据库,完成“save(customer2)”,此处不再有冲突。
 
=== 应用场景一:解决非线程安全的访问(分隔操作) ===
由于在事务中进行了不正确的操作,或者在多线程操作同一事务,就可能造成“线程安全的访问”的异常。
<span style="color: green">“an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
  net.sf.hibernate.AssertionFailure: possible nonthreadsafe access to session”</span>
 
'''示例''':(会产生“<span style="color: green">'''net.sf.hibernate.AssertionFailure'''</span>”异常)
: 主键生成策略为 uuid
: <syntaxhighlight lang="Java" highlight="">
public void test_flush() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
// 创建 user 对象
User user = new User();
user.setName("李四");
user.setPassword("123");
user.setCreateTime(new Date());
user.setExpireTime(new Date());
// 主键生成策略为 uuid,所以不会立即发出 insert 语句
session.save(user);
// flush
//session.flush();
 
// 从 session 缓存中逐出该对象
session.evict(user);
tx.commit();
}
</syntaxhighlight>
'''分析''':
# 由于主键生成策略为 uuid,所以 save() 不会立即发出 insert 语句,而是:将 user 对象放入 persistenceContext,将 user 相关操作放入 actionQueue;
# 而由于 save 之后的 evict 操作,会将 user 对象从缓存 persistenceContext 逐出,所以 commit 时即使 actionQueue 有相关 insert 操作也不能完成,从而出现异常。
'''解决''':
: 如上,在“// flush”注释处,使用“session.flush()”,则:
# 在 evict 操作之前,已强制刷出 user 到数据库:actionQueue 中 user 相关操作执行完成,persistenceContext 中 user 状态也已更新到数据库;
# commit 时,不再执行 user 相关操作,直接提交事务即可。
 
 
=== commit() 与 flush() ===
<big>'''Session.flush()'''</big>:用于进行缓存清理。
    ——  即:<span style="color: blue">'''发送并执行 SQL,但不提交事务。'''</span>
<big>'''Transaction.commit()'''</big>:(非 FlushMode.MANUAL/NEVER 时)先通过隐式调用 flush() 执行“缓存清理”,然后提交事务。
    ——  即:<span style="color: blue">'''发送并执行 SQL,然后提交事务。'''</span>
<big>'''注意'''</big>:<span style="color: blue">'''flush() 方法并不意味着数据已经持久化到数据库中了,在没有提交事务前,所有的数据都并没有真正被持久化。'''</span>
    —— 如果数据库隔离级别为“'''READ UNCOMMITTED'''”,则可能看到 flush 的数据。
 
所谓“隐式调用”:
: 通过 SessionIml 的 flushBeforeTransactionCompletion(),调用 doFlush() 方法;
: 而并不直接调用 Session.flush();
: <syntaxhighlight lang="Java" highlight="">
...
public void flushBeforeTransactionCompletion() {
final boolean doFlush = isTransactionFlushable()
&& getHibernateFlushMode() != FlushMode.MANUAL;
 
try {
if ( doFlush ) {
managedFlush();
}
}
catch (RuntimeException re) {
throw ExceptionMapperStandardImpl.INSTANCE.mapManagedFlushFailure( "error during managed flush", re, this );
}
}
   
...
private void managedFlush() {
if ( isClosed() && !waitingForAutoClose ) {
log.trace( "Skipping auto-flush due to session closed" );
return;
}
log.trace( "Automatically flushing session" );
doFlush();
}
</syntaxhighlight>
 
== 源码:相关类/属性 ==
SessionImpl 类(间接实现了 Session 接口)中有多个属性与缓存相关。
Hibernate 不同版本的源码实现有不同,以下以 5.4.11.Final 为例。
 
# '''actionQueue''':负责维护与事件相关的动作队列。
#: 即:存放各种类型的临时的 SQL 语句,在 flush 时执行使用。
#* 类型:ActionQueue;
# '''persistenceContext''':负责在整个生命周期中维持 PersistenceContext 的状态。
#* 类型:StatefulPersistenceContext(实现了 PersistenceContext 接口);
 
=== ActionQueue ===
ActionQueue 类:将 DML 操作作为会话的 transactional-write-behind 语义的一部分排队。DML 操作在这里排队,直到刷新强制它们对数据库执行。
 
ActionQueue 包含了多个不同类型的 '''ExecutableList(可执行列表)''':
: <syntaxhighlight lang="Java" highlight="4-6">
// Object insertions, updates, and deletions have list semantics because
// they must happen in the right order so as to respect referential
// integrity
private ExecutableList<AbstractEntityInsertAction> insertions;
private ExecutableList<EntityDeleteAction> deletions;
private ExecutableList<EntityUpdateAction> updates;
 
// Actually the semantics of the next three are really "Bag"
// Note that, unlike objects, collection insertions, updates,
// deletions are not really remembered between flushes. We
// just re-use the same Lists for convenience.
private ExecutableList<CollectionRecreateAction> collectionCreations;
private ExecutableList<CollectionUpdateAction> collectionUpdates;
private ExecutableList<QueuedOperationCollectionAction> collectionQueuedOps;
private ExecutableList<CollectionRemoveAction> collectionRemovals;
 
// TODO: The removeOrphan concept is a temporary "hack" for HHH-6484.  This should be removed once action/task
// ordering is improved.
private ExecutableList<OrphanRemovalAction> orphanRemovals;
</syntaxhighlight>
*(在 3.5.6.Final 等版本中,为 ArrayList 结构,而非 ExecutableList)
 
=== StatefulPersistenceContext ===
StatefulPersistenceContext 类:代表 Hibernate 正在追踪的“'''实体'''、'''集合'''、'''快照'''、'''代理'''”的状态。
* PersistenceContext 与 SessionImpl 为“一对一”的关系。
* 事件侦听器和其他 Session collaborators 使用 PersistentContext 来驱动其处理。
 
StatefulPersistenceContext 包含了:
# entitiesByKey:按 EntityKey 加载的实体实例;
#: 类型:HashMap<EntityKey, Object>
# entitiesByUniqueKey:按 EntityUniqueKey 加载的实体实例;
#: 类型:HashMap<EntityUniqueKey, Object>
# <s>'''entityEntries''':维护“实体实例”与“EntityEntry 实例”的标识映射;</s>
#*【已废弃(存在于  3.5.6.Final 等版本中)。  ——现由 entityEntryContext 替代】
# '''entityEntryContext''':用于维护(“与拥有此 EntityEntryContext 的 Session 关联的”)实体 和(“与实体相关的”)EntityEntry 的关系;
#: 类型:EntityEntryContext
 
=== EntityEntry ===
EntityEntry 接口:用于表示一个对象(相对于其持久状态的)当前状态。  ——【在  3.5.6.Final 等版本中为 EntityEntry 类】
现在,实际使用的是:ImmutableEntityEntry 类 -> 继承于 AbstractEntityEntry 类  -> 实现了 EntityEntry 接口。
 
EntityEntry 包含了:
# <s>existsInDatabase:表示该 persistence 对象是否存到数据库的状态;</s>
#: 类型:boolean
#: 如果 existsInDatabase=false,则对象相关的 insert 语句在 actionQueue 中的 insertions 中;
#: 如果 existsInDatabase=true,则对象相关 insert 语句已经执行过了。
#*【已废弃(存在于  3.5.6.Final 等版本中)。  ——现作为 '''AbstractEntityEntry''' 类(实现了 EntityEntry 接口)的 '''compressedState''' 字段的一部分存在 】
# isExistsInDatabase() 方法:用于判断该 persistence 对象是否存到数据库。
#: 返回值:boolean

2022年6月15日 (三) 13:05的最新版本


关于

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

相关方法

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 缓存作用

作用:

  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 会依据 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 会对 Session 中的持久状态的对象进行检测,判断对象的数据是否发生了改变,这种判断称为“脏检查”。


原理:快照
1、Hibernate 向“一级缓存”放入数据时,同时复制一份数据放入到 Hibernate “快照区”中。
2、持久化对象发生更改时,只会修改缓存中数据,并不会修改对象的快照,也不会直接更改到数据库。
3、Session刷新缓存时,通过 OID 判断 Session 缓存中的对象和快照中的对象是否一致:
  ——如果两个对象中的属性发生变化(脏对象),则执行 update 语句, 将缓存的内容同步到数据库,并更新快照。
  ——如果数据一致,则不执行 update 语句。


快照:相当于数据库数据的副本,确保缓存的数据与数据库一致

通常脏数据的检查有如下两种办法:

  1. 数据对象监控:通过“拦截器”对数据对象的 setter 方法进行监控来实现的。(类似于数据库中的触发器)
    当某一个对象的属性调用了 setter 方法而发生了改变,这时拦截器会捕获这个动作,并且将改属性标志为已经改变,在之后的数据库操作时将其更新到数据库中。
    • 特点:提高了数据更新的同步性。【实时更新】
      但,如果同一实体对象发生多次属性变化,将会造成大量拦截器回调方法的调用。这些拦截器都是通过 Dynamic Proxy 或者 CGLIB 实现的,在执行时都会付出一定的执行代价,所以有可能造成更新操作的较大延时。
  2. 数据版本比对:通过保存数据对象的最近读取版本来实现。
    在持久化框架中保存数据对象的最近读取版本,当提交数据时将提交的数据与这个保存的版本进行比对,如果发现发生了变化则将其同步跟新到数据库中。
    • 特点:降低了数据更新的同步性。【非实时更新】
      但,如果同一实体对象发生多次属性变化,由于持久层框架缓存的存在,比对版本时可以充分利用缓存,这反而减少了更新数据的延迟。
Hibernate 采用的是“数据版本比对”的方法来进行脏数据检查的。

缓存清理机制(缓存刷出)

缓存清理:在某些时间点,Session 会对持久化状态的缓存数据进行检查(脏检查),并执行一些“必需的 SQL 语句”来把内存中的对象的状态同步到数据库中。


这一过程也就是“缓存刷出(flush)”,其目的是“同步缓存数据到数据库”。  ——【注意:“缓存清理”并非“清空缓存”!!!】

清理过程


缓存清理过程中,Session 会对相关 SQL 语句进行合并优化,同时按照一定的顺序来执行 SQL 语句。

【SQL 语句与操作,在数据或顺序上都并非一一对应】

SQL 执行顺序

清理过程中,涉及的 SQL 语句,必须按照正确的顺序发生,以保证功能的完整性。

注意:
1、上一顺序的所有操作(如,所有的“save()”)执行完成后,才会执行下一顺寻的操作(如,所有的“update()”)。
2、仅能保证其前后顺序,而不能确定何时执行这些语句(除非显示调用 flush())。

具体会按照下面的顺序发出执行:【???集合???】

  1. 所有:对实体进行插入的语句。
    • 其顺序按照对象执行 Session.save() 的时间顺序;
  2. 所有:对实体进行更新的语句;
  3. 所有:对集合进行删除的语句;
  4. 所有:对集合元素进行删除,更新或者插入的语句;
  5. 所有:对集合进行插入的语句;
  6. 所有:对实体进行删除的语句。
    • 其顺序按照对象执行 Session.delete() 的时间顺序;

即,ActionQueue 类的静态代码块中,EXECUTABLE_LISTS_MAP 对 ExecutableList 进行 put 的顺序。


如上,对于实体对象,其顺序是:“insert”(save)->“update”->“delete例外情况:
    如果对象使用 native 生成器来生成 OID,那么当调用 Session 的 save() 方法保存该对象时,会立即执行向数据库插入该实体的 insert 语句。

原因:
   1、在 save() 方法后,必须保证对象的 OID 是存在的;
   2、而 native 作为主键生成方式,必须在数据库执行 insert 语句后才能得到 OID

SQL 合并优化

Session 能够把几条相关的 SQL 语句合并为一条 SQL 语句,以便减少访问数据库的次数,从而提高应用程序的数据访问性能。


合并一般是针对同一对象的同类动作。(如,对同一个持久化对象的多条 update,就可以优化成最终的一条 update)

即:
1、不同对象的操作不能合并。
2、不同操作一般不会一起优化。(如,save() 和 update())

清理时机(触发策略)


当 Session 缓存中对象的属性发生了变化,Session 并不会立即清理缓存和执行相关的 SQL update 语句,而是在特定的时间点才清理缓存。

默认清理时机

默认情况:FlushMode.AUTO

默认情况下的清理时机:

  1. 提交前:当调用 Transaction 的 commit() 方法时,会先清理缓存,然后再向数据库提交事务。
    • commit() 会隐式调用 flush() 方法。
    • 之所以在事务快结束时清理缓存:
      1. 可以减少访问数据库的频率
      2. 可以尽可能缩短当前事务对数据库中相关资源的锁定时间
  2. 查询前:当应用程序调用 Session 的 list() 或者 iterate() 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存。
    • 以此保证查询结果能反映持久化对象的最新状态。
  3. flush:当应用程序显示调用 Session 的 flush() 方法的时候。

如下图:

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

设置清理时机

可以通过 session.setFlushMode(FlushMode) 来设置 Session 缓存清理时机。

【设置 FlushMode,应该在 session 开启事务之前】

FlushMode 的枚举值:

  1. “FlushMode.ALWAYS”:每次查询之前都“清理缓存”;
  2. “FlushMode.AUTO”:有时会在执行查询之前“清理缓存”,以确保查询永远不会返回过时状态。
    • 【默认】
  3. “FlushMode.COMMIT”:当 Transaction.commit() 被调用时“清理缓存”。
  4. “FlushMode.MANUAL”:仅在显示调用 Session.flush() 时才“清理缓存”;
    • 在性能优化时可能用:
      比如 session 只做查询操作时,就不需要与数据库同步。
  5. “FlushMode.NEVER”:永不“清理缓存”。
    • 【已废弃。 ——由“FlushMode.MANUAL”代替】

flush()

flush() 执行过程,就是“缓存清理”过程:

    由于 flush() 的特殊处理机制,可能会破坏事务提交的完整性,所以一般不建议使用此方法。

    但是在一些复杂的事务处理过程中,使用 flush() 可以规避一些不可预见的异常情况。

应用场景一:解决同一事务的数据冲突(分隔 SQL)

在有多个 SQL 操作时,可能由于不同顺序等级(save、update、delete)的交叉执行,导致数据冲突。

此时,可以用 session.flush() 打断其原本的执行顺序,让它先干完一部分,来减少冲突。

示例:(会产生“主键冲突”)

ID 为主键
	public void test_flush() { 
		Session session = HibernateUtils.openSession(); 
		Transaction tx = session.beginTransaction(); 
		
		// 创建 customer1 对象
		Customer customerl = new Customer();
		customerl.setID(10001);
		customerl.setName(张三);
		session.save(customerl);
		
		// 更新 customer1 对象
		customerl.setID(10011);
		session.update(customerl);
		
		// flush
		//session.flush();
		
		// 创建 customer2 对象
		Customer customer2 = new Customer();
		customer2.setID(10001);
		customer2.setName(李四);
		session.save(customer2);
		
		tx.commit();
	}

分析

  1. 如上代码,会产生一次 flush 操作(commit 时);
  2. 由于 flush 相关的 SQL 顺序为:“save(customerl)”->“save(customer2)”->“update(customerl)”;
    • 【所有 save 操作完成之后,再进行所有 update 操作,最后进行所有 delete 操作】
  3. 所以两次 save 的对象具有相同的主键(ID),出现冲突。

解决

如上,在“// flush”注释处,使用“session.flush()”,则将产生两次 flush:
  1. 第一次 flush,强制刷出 customerl 的数据到数据库,将完成“save(customerl)”和“update(customerl)”;
  2. 第二次 flush(commit 时),仅刷出 customer2 的数据到数据库,完成“save(customer2)”,此处不再有冲突。

应用场景一:解决非线程安全的访问(分隔操作)

由于在事务中进行了不正确的操作,或者在多线程操作同一事务,就可能造成“线程安全的访问”的异常。

“an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
  net.sf.hibernate.AssertionFailure: possible nonthreadsafe access to session”

示例:(会产生“net.sf.hibernate.AssertionFailure”异常)

主键生成策略为 uuid
	public void test_flush() { 
		Session session = HibernateUtils.openSession(); 
		Transaction tx = session.beginTransaction(); 
		
		// 创建 user 对象
		User user = new User();
		user.setName("李四");
		user.setPassword("123");
		user.setCreateTime(new Date());
		user.setExpireTime(new Date());
		// 主键生成策略为 uuid,所以不会立即发出 insert 语句
		session.save(user);
		
		// flush
		//session.flush();

		// 从 session 缓存中逐出该对象
		session.evict(user);
		
		
		tx.commit();
	}

分析

  1. 由于主键生成策略为 uuid,所以 save() 不会立即发出 insert 语句,而是:将 user 对象放入 persistenceContext,将 user 相关操作放入 actionQueue;
  2. 而由于 save 之后的 evict 操作,会将 user 对象从缓存 persistenceContext 逐出,所以 commit 时即使 actionQueue 有相关 insert 操作也不能完成,从而出现异常。

解决

如上,在“// flush”注释处,使用“session.flush()”,则:
  1. 在 evict 操作之前,已强制刷出 user 到数据库:actionQueue 中 user 相关操作执行完成,persistenceContext 中 user 状态也已更新到数据库;
  2. commit 时,不再执行 user 相关操作,直接提交事务即可。


commit() 与 flush()

Session.flush():用于进行缓存清理。
    ——  即:发送并执行 SQL,但不提交事务。

Transaction.commit():(非 FlushMode.MANUAL/NEVER 时)先通过隐式调用 flush() 执行“缓存清理”,然后提交事务。
    ——  即:发送并执行 SQL,然后提交事务。


注意flush() 方法并不意味着数据已经持久化到数据库中了,在没有提交事务前,所有的数据都并没有真正被持久化。
    —— 如果数据库隔离级别为“READ UNCOMMITTED”,则可能看到 flush 的数据。

所谓“隐式调用”:

通过 SessionIml 的 flushBeforeTransactionCompletion(),调用 doFlush() 方法;
而并不直接调用 Session.flush();
	...
	public void flushBeforeTransactionCompletion() {
		final boolean doFlush = isTransactionFlushable()
				&& getHibernateFlushMode() != FlushMode.MANUAL;

		try {
			if ( doFlush ) {
				managedFlush();
			}
		}
		catch (RuntimeException re) {
			throw ExceptionMapperStandardImpl.INSTANCE.mapManagedFlushFailure( "error during managed flush", re, this );
		}
	}
    
	...
	private void managedFlush() {
		if ( isClosed() && !waitingForAutoClose ) {
			log.trace( "Skipping auto-flush due to session closed" );
			return;
		}
		log.trace( "Automatically flushing session" );
		doFlush();
	}

源码:相关类/属性

SessionImpl 类(间接实现了 Session 接口)中有多个属性与缓存相关。


Hibernate 不同版本的源码实现有不同,以下以 5.4.11.Final 为例。
  1. actionQueue:负责维护与事件相关的动作队列。
    即:存放各种类型的临时的 SQL 语句,在 flush 时执行使用。
    • 类型:ActionQueue;
  2. persistenceContext:负责在整个生命周期中维持 PersistenceContext 的状态。
    • 类型:StatefulPersistenceContext(实现了 PersistenceContext 接口);

ActionQueue

ActionQueue 类:将 DML 操作作为会话的 transactional-write-behind 语义的一部分排队。DML 操作在这里排队,直到刷新强制它们对数据库执行。

ActionQueue 包含了多个不同类型的 ExecutableList(可执行列表)

	// Object insertions, updates, and deletions have list semantics because
	// they must happen in the right order so as to respect referential
	// integrity
	private ExecutableList<AbstractEntityInsertAction> insertions;
	private ExecutableList<EntityDeleteAction> deletions;
	private ExecutableList<EntityUpdateAction> updates;

	// Actually the semantics of the next three are really "Bag"
	// Note that, unlike objects, collection insertions, updates,
	// deletions are not really remembered between flushes. We
	// just re-use the same Lists for convenience.
	private ExecutableList<CollectionRecreateAction> collectionCreations;
	private ExecutableList<CollectionUpdateAction> collectionUpdates;
	private ExecutableList<QueuedOperationCollectionAction> collectionQueuedOps;
	private ExecutableList<CollectionRemoveAction> collectionRemovals;

	// TODO: The removeOrphan concept is a temporary "hack" for HHH-6484.  This should be removed once action/task
	// ordering is improved.
	private ExecutableList<OrphanRemovalAction> orphanRemovals;
  • (在 3.5.6.Final 等版本中,为 ArrayList 结构,而非 ExecutableList)

StatefulPersistenceContext

StatefulPersistenceContext 类:代表 Hibernate 正在追踪的“实体集合快照代理”的状态。

* PersistenceContext 与 SessionImpl 为“一对一”的关系。
* 事件侦听器和其他 Session collaborators 使用 PersistentContext 来驱动其处理。

StatefulPersistenceContext 包含了:

  1. entitiesByKey:按 EntityKey 加载的实体实例;
    类型:HashMap<EntityKey, Object>
  2. entitiesByUniqueKey:按 EntityUniqueKey 加载的实体实例;
    类型:HashMap<EntityUniqueKey, Object>
  3. entityEntries:维护“实体实例”与“EntityEntry 实例”的标识映射;
    • 【已废弃(存在于 3.5.6.Final 等版本中)。 ——现由 entityEntryContext 替代】
  4. entityEntryContext:用于维护(“与拥有此 EntityEntryContext 的 Session 关联的”)实体 和(“与实体相关的”)EntityEntry 的关系;
    类型:EntityEntryContext

EntityEntry

EntityEntry 接口:用于表示一个对象(相对于其持久状态的)当前状态。  ——【在  3.5.6.Final 等版本中为 EntityEntry 类】

现在,实际使用的是:ImmutableEntityEntry 类 -> 继承于 AbstractEntityEntry 类  -> 实现了 EntityEntry 接口。

EntityEntry 包含了:

  1. existsInDatabase:表示该 persistence 对象是否存到数据库的状态;
    类型:boolean
    如果 existsInDatabase=false,则对象相关的 insert 语句在 actionQueue 中的 insertions 中;
    如果 existsInDatabase=true,则对象相关 insert 语句已经执行过了。
    • 【已废弃(存在于 3.5.6.Final 等版本中)。 ——现作为 AbstractEntityEntry 类(实现了 EntityEntry 接口)的 compressedState 字段的一部分存在 】
  2. isExistsInDatabase() 方法:用于判断该 persistence 对象是否存到数据库。
    返回值:boolean