“Hibernate笔记 4:核心知识:Session缓存”的版本间差异
跳到导航
跳到搜索
(创建页面,内容为“category:Hibernate == 关于 == Session 缓存:是一块内存空间,用来存放相互管理的对象。 作用: # 减少访问数据库的频率。【缓存查询数据】 # 保证缓存中的对象与数据库中的相关记录保持同步。【持久态自动更新】 === 缓存查询数据 === 在使用 Hibernate 查询对象的时候,首先会使用对象属性的 <span style="color: blue">'''OID'''</span> 值在 Hibernate 的一级缓存…”) |
|||
第102行: | 第102行: | ||
Hibernate 采用的是“数据版本比对”的方法来进行脏数据检查的。 | Hibernate 采用的是“数据版本比对”的方法来进行脏数据检查的。 | ||
== | == 缓存清理机制(缓存刷出) == | ||
<span style="color: blue; font-size: 120%">'''缓存清理'''</span> | <span style="color: blue; font-size: 120%">'''缓存清理'''</span>:在某些时间点,Session 会对持久化状态的缓存数据进行检查(<span style="color: blue">'''脏检查'''</span>),并执行一些“必需的 SQL 语句”来把内存中的对象的状态同步到数据库中。 | ||
这一过程也就是“<span style="color: blue; font-size: 120%">'''缓存刷出(flush)'''</span>”,其目的是“同步缓存数据到数据库”。 ——【注意:“缓存清理”并非“清空缓存”!!!】 | |||
=== 清理过程(SQL 执行顺序) === | |||
清理过程中,涉及的 SQL 语句,必须按照正确的顺序发生,以保证功能的完整性。 | |||
注意: | |||
1、上一顺序的所有操作(如,所有的“save()”)执行完成后,才会执行下一顺寻的操作(如,所有的“update()”)。 | |||
2、仅能保证其前后顺序,而不能确定何时执行这些语句(除非显示调用 flush())。 | |||
具体会按照下面的顺序发出执行:【???集合???】 | |||
# 所有:对'''实体'''进行'''插入'''的语句。 | |||
#* 其顺序按照对象执行 Session.save() 的时间顺序; | |||
# 所有:对'''实体'''进行'''更新'''的语句; | |||
# 所有:对集合进行删除的语句; | |||
# 所有:对集合元素进行删除,更新或者插入的语句; | |||
# 所有:对集合进行插入的语句; | |||
# 所有:对'''实体'''进行'''删除'''的语句。 | |||
#* 其顺序按照对象执行 Session.delete() 的时间顺序; | |||
即,ActionQueue 类的静态代码块中,EXECUTABLE_LISTS_MAP 对 ExecutableList 进行 put 的顺序。 | |||
如上,对于实体对象,其顺序是:“'''insert'''”(save)->“'''update'''”->“'''delete'''”。 | |||
'''例外情况''': | |||
如果对象使用 <span style="color: blue">'''native'''</span> 生成器来生成 '''OID''',那么当调用 Session 的 <span style="color: blue">'''save()'''</span> 方法保存该对象时,会立即执行向数据库插入该实体的 '''insert''' 语句。 | |||
'''原因''': | |||
1、在 save() 方法后,必须保证对象的 OID 是存在的; | |||
2、而 native 作为主键生成方式,必须在数据库执行 insert 语句后才能得到 '''OID'''。 | |||
=== 清理时机(触发策略) === | |||
=== | |||
当 Session 缓存中对象的属性发生了变化,Session '''并不会立即清理缓存和执行相关的 SQL update 语句''',而是在特定的时间点才清理缓存,这使得 Session 能够把几条'''相关的 SQL 语句合并为一条 SQL 语句''',以便减少访问数据库的次数,从而提高应用程序的数据访问性能。 | 当 Session 缓存中对象的属性发生了变化,Session '''并不会立即清理缓存和执行相关的 SQL update 语句''',而是在特定的时间点才清理缓存,这使得 Session 能够把几条'''相关的 SQL 语句合并为一条 SQL 语句''',以便减少访问数据库的次数,从而提高应用程序的数据访问性能。 | ||
==== 默认清理时机 ==== | ==== 默认清理时机 ==== | ||
默认情况(FlushMode.AUTO)下的清理时机: | |||
# '''提交前''':当调用 Transaction 的 commit() | # '''提交前''':当调用 Transaction 的 '''commit()''' 方法时,会先清理缓存,然后再向数据库提交事务。 | ||
#* commit() 会隐式调用 flush() 方法。 | |||
#* commit() 会隐式调用 flush() | #* 之所以在事务快结束时清理缓存: | ||
# '''查询前''':当应用程序调用 Session 的 | #*# '''可以减少访问数据库的频率'''; | ||
# '''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. | ! FlushMode !! Session.list()/iterate() !! Session.commit() !! Session.flush() | ||
|- | |- | ||
| FlushMode.AUTO || ✔ || ✔ || ✔ | | FlushMode.AUTO || ✔ || ✔ || ✔ | ||
第137行: | 第164行: | ||
:* 清理(✔),不清理(✘) | :* 清理(✔),不清理(✘) | ||
==== | ==== 设置清理时机 ==== | ||
可以通过 <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() 与 commit() === | |||
辨析: | |||
# flush():执行缓存清理。 | |||
#* 其执行过程,就是“缓存清理”过程。 | |||
# commit():提交事务。 | |||
#* 会隐式调用 flush(),以执行“缓存清理”。(非 FlushMode.MANUAL/NEVER 时) | |||
所谓“隐式调用”: | |||
: 通过 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> | |||
'''注意''': | |||
<span style="color: blue">'''flush() 方法并不意味着数据已经持久化到数据库中了,在没有提交事务前(commit()),所有的数据都并没有真正被持久化。'''</span> | |||
=== flush() 与 commit() === |
2022年6月15日 (三) 10:31的版本
关于
Session 缓存:是一块内存空间,用来存放相互管理的对象。
作用:
- 减少访问数据库的频率。【缓存查询数据】
- 保证缓存中的对象与数据库中的相关记录保持同步。【持久态自动更新】
缓存查询数据
在使用 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 一级缓存的“脏检查和缓存清理机制”。
相关方法
Session 接口(及其他查询接口,如:Query)的相关方法会导致 Session 缓存的:增加、删除、清空。
- 增加:
- 调用 Session 接口的 save()、update()、saveOrUpdate() 时:如果 Session 缓存中没有相应的对象,Hibernate 就会把从数据库中查询到的相应对象信息加入缓存。
- 调用 Session 接口的 load()、get(),及 Query接口的 list()、iterator() 时:如果 Session 缓存中有相应的对象,则直接取出并返回;否则,从数据库查询数据并放到缓存。
- 删除:调用 Session 的 evict() 方法时,会从 Session 缓存移除对应数据。
- 清空:调用 Session 的 clear()、close() 方法时,会清空 Session 缓存。
脏检查机制(快照)
脏检查:当刷新缓存(缓存清理)时,Hibernate 会对 Session 中的持久状态的对象进行检测,判断对象的数据是否发生了改变,这种判断称为“脏检查”。 原理:“快照” 1、Hibernate 向“一级缓存”放入数据时,同时复制一份数据放入到 Hibernate “快照区”中。 2、持久化对象发生更改时,只会修改缓存中数据,并不会修改对象的快照,也不会直接更改到数据库。 3、Session刷新缓存时,通过 OID 判断 Session 缓存中的对象和快照中的对象是否一致: ——如果两个对象中的属性发生变化(脏对象),则执行 update 语句, 将缓存的内容同步到数据库,并更新快照。 ——如果数据一致,则不执行 update 语句。 快照:相当于数据库数据的副本,确保缓存的数据与数据库一致。
通常脏数据的检查有如下两种办法:
- 数据对象监控:通过“拦截器”对数据对象的 setter 方法进行监控来实现的。(类似于数据库中的触发器)
- 当某一个对象的属性调用了 setter 方法而发生了改变,这时拦截器会捕获这个动作,并且将改属性标志为已经改变,在之后的数据库操作时将其更新到数据库中。
- 特点:提高了数据更新的同步性。【实时更新】
- 但,如果同一实体对象发生多次属性变化,将会造成大量拦截器回调方法的调用。这些拦截器都是通过 Dynamic Proxy 或者 CGLIB 实现的,在执行时都会付出一定的执行代价,所以有可能造成更新操作的较大延时。
- 数据版本比对:通过保存数据对象的最近读取版本来实现。
- 在持久化框架中保存数据对象的最近读取版本,当提交数据时将提交的数据与这个保存的版本进行比对,如果发现发生了变化则将其同步跟新到数据库中。
- 特点:降低了数据更新的同步性。【非实时更新】
- 但,如果同一实体对象发生多次属性变化,由于持久层框架缓存的存在,比对版本时可以充分利用缓存,这反而减少了更新数据的延迟。
Hibernate 采用的是“数据版本比对”的方法来进行脏数据检查的。
缓存清理机制(缓存刷出)
缓存清理:在某些时间点,Session 会对持久化状态的缓存数据进行检查(脏检查),并执行一些“必需的 SQL 语句”来把内存中的对象的状态同步到数据库中。 这一过程也就是“缓存刷出(flush)”,其目的是“同步缓存数据到数据库”。 ——【注意:“缓存清理”并非“清空缓存”!!!】
清理过程(SQL 执行顺序)
清理过程中,涉及的 SQL 语句,必须按照正确的顺序发生,以保证功能的完整性。 注意: 1、上一顺序的所有操作(如,所有的“save()”)执行完成后,才会执行下一顺寻的操作(如,所有的“update()”)。 2、仅能保证其前后顺序,而不能确定何时执行这些语句(除非显示调用 flush())。
具体会按照下面的顺序发出执行:【???集合???】
- 所有:对实体进行插入的语句。
- 其顺序按照对象执行 Session.save() 的时间顺序;
- 所有:对实体进行更新的语句;
- 所有:对集合进行删除的语句;
- 所有:对集合元素进行删除,更新或者插入的语句;
- 所有:对集合进行插入的语句;
- 所有:对实体进行删除的语句。
- 其顺序按照对象执行 Session.delete() 的时间顺序;
即,ActionQueue 类的静态代码块中,EXECUTABLE_LISTS_MAP 对 ExecutableList 进行 put 的顺序。
如上,对于实体对象,其顺序是:“insert”(save)->“update”->“delete”。 例外情况: 如果对象使用 native 生成器来生成 OID,那么当调用 Session 的 save() 方法保存该对象时,会立即执行向数据库插入该实体的 insert 语句。 原因: 1、在 save() 方法后,必须保证对象的 OID 是存在的; 2、而 native 作为主键生成方式,必须在数据库执行 insert 语句后才能得到 OID。
清理时机(触发策略)
当 Session 缓存中对象的属性发生了变化,Session 并不会立即清理缓存和执行相关的 SQL update 语句,而是在特定的时间点才清理缓存,这使得 Session 能够把几条相关的 SQL 语句合并为一条 SQL 语句,以便减少访问数据库的次数,从而提高应用程序的数据访问性能。
默认清理时机
默认情况(FlushMode.AUTO)下的清理时机:
- 提交前:当调用 Transaction 的 commit() 方法时,会先清理缓存,然后再向数据库提交事务。
- commit() 会隐式调用 flush() 方法。
- 之所以在事务快结束时清理缓存:
- 可以减少访问数据库的频率;
- 可以尽可能缩短当前事务对数据库中相关资源的锁定时间;
- 查询前:当应用程序调用 Session 的 list() 或者 iterate() 时,如果缓存中的持久化对象的属性发生了变化,就会先清理缓存。
- 以此保证查询结果能反映持久化对象的最新状态。
- flush:当应用程序显示调用 Session 的 flush() 方法的时候。
如下图:
FlushMode Session.list()/iterate() Session.commit() Session.flush() FlushMode.AUTO ✔ ✔ ✔ FlushMode.COMMIT ✘ ✔ ✔ FlushMode.NEVER ✘ ✘ ✔
- 清理(✔),不清理(✘)
设置清理时机
可以通过 session.setFlushMode(FlushMode) 来设置 Session 缓存清理时机。
【设置 FlushMode,应该在 session 开启事务之前】
FlushMode 的枚举值:
- “FlushMode.ALWAYS”:每次查询之前都“清理缓存”;
- “FlushMode.AUTO”:有时会在执行查询之前“清理缓存”,以确保查询永远不会返回过时状态。
- 【默认】
- “FlushMode.COMMIT”:当 Transaction.commit() 被调用时“清理缓存”。
- “FlushMode.MANUAL”:仅在显示调用 Session.flush() 时才“清理缓存”;
- 在性能优化时可能用:
- 比如 session 只做查询操作时,就不需要与数据库同步。
- 在性能优化时可能用:
- “FlushMode.NEVER”:永不“清理缓存”。
- 【已废弃。 ——由“FlushMode.MANUAL”代替】
flush()
flush() 与 commit()
辨析:
- flush():执行缓存清理。
- 其执行过程,就是“缓存清理”过程。
- commit():提交事务。
- 会隐式调用 flush(),以执行“缓存清理”。(非 FlushMode.MANUAL/NEVER 时)
所谓“隐式调用”:
- 通过 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(); }
注意:
flush() 方法并不意味着数据已经持久化到数据库中了,在没有提交事务前(commit()),所有的数据都并没有真正被持久化。