文章目录
Spring Data JPA想要学得好,缓存机制掌握好
本文章主要对JPA进行简单的介绍,主要重点在于JPA的一级缓存机制,会带领大家浅读一下具体实现的Hibernate中的源码。
Hibernate、JPA与Spring Data JPA之间的关系
JPA是一套规范,内部是有接口和抽象类组成的。hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。以上就是对hibernate、JPA与Spring Data JPA三者之间的关系说明。
参考:详谈hibernate,jpa与spring data jpa三者之间的关系
所以虽然我们标题是《Spring Data JPA想要学得好,缓存机制掌握好》,但实际上这里我们在探讨的是具体实现——Hibernate的缓存
JPA的EntityManager接口与Hibernate的Session接口
首先
EntityManager
和
Session
都是接口。
然后
Session
是继承于
EntityManager
的。
所以可以理解为
EntityManager
是对JPA持久化上下文交互的抽象,而
Session
接口是 Hibernate 向应用程序提供的操纵对数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载Java 对象的方法。而Hibernate是JPA的具体实现,所以
Session
自然是继承于
EntityManager
然后我们可以看
EntityManager
具体的实现类其实是Hibernate相关的类
参考: 比较JPA的EntityManager接口与Hibernate的Session接口
Hibernate的缓存
Hibernate缓存包括两大类:一级缓存和二级缓存。
一级缓存又称为"Session的缓存",它是内置的,不能被卸载(不能被卸载的意思就是这种缓存不具有可选性,必须有的功能,不可以取消session缓存)。由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存在第一级缓存中,持久化类的每个实例都具有唯一的OID。我们使用
@Transactional
注解时,
JpaTransactionManager
会在开启事务前打开一个session,将事务绑定在这个session上,事务结束session关闭。
二级缓存又称为"SessionFactory的缓存",由于
SessionFactory
对象的生命周期和应用程序的整个过程对应,因此二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略。第二级缓存是可选的,是一个可配置的插件,在默认情况下,
SessionFactory
不会启用这个插件,二级缓存应用场景局限性比较大,适用于数据要求的实时性和准确性不高、变动很少的情况。
原文链接:https://blog.csdn.net/qq_34485381/article/details/107117550
Hibernate的一级缓存(Session的缓存)
本篇博文主要探讨的只是Hibernate的一级缓存,因为他是内置且必须的,而二级缓存基本很少使用。
我们在使用Spring Data JPA的时候,其实都会使用到Hibernate的一级缓存,所以了解缓存机制就变得尤为重要,下面举一个例子来看看:
@TransactionalpublicUsertest(){List<ContactInfo> contactInfos =newArrayList<>(List.of(ContactInfo.builder().address("test address").phoneNumber("1234").build()));User user =User.builder().name("kevin").contactInfos(contactInfos).build();
contactInfos.get(0).setUser(user);
userRepository.save(user);
userRepository.findById(1L);
userRepository.findById(1L);return user;}
上面这个事务方法中会执行几次select语句?是两次吗?不真正答案是0次。可能有人就很好奇,明明调用了两次
findById
方法,却没有执行select语句,这是为什么呢?其实就是因为使用到了我们的一级缓存。
当我们执行
save
方法的时候,其实是会把保存后的数据存入到缓存中的,如下图:
浅读缓存源码解密缓存过程
那可能有的小伙伴会好奇,那数据是缓存到了哪里呢?接下来我们就通过debug的方式,来看一下在保存还有后续的查询过程是怎样的。
我们先来说结论,缓存的数据是存在
SessionImpl
类中的
StatefulPersistenceContext persistenceContext
属性上。
在
StatefulPersistenceContext
类中,其中有一个
HashMap<EntityKey, Object> entitiesByKey
属性,这个
entitiesByKey
属性字段的作用就是用来缓存数据的,这个
StatefulPersistenceContext
类中还有其他一些 Java 集合, 这些 Java 集合构成了 Session 缓存。
这次debug我们主要关注这个
entitiesByKey
属性字段
我们来看当我们执行
userRepository.save(user);
这句的时候,最终会把存入的数据加入到缓存中,通过调用
StatefulPersistenceContext
类中的
addEntity
方法,把数据存入到
entitiesByKey
属性字段上。当然不只是
user
数据会存入,级联的
contactInfo
的数据也是会存入的,这里我们就不过多展示了。
然后当我们调用
userRepository.findById(1L);
方法的时候,会先从缓存中获取数据,缓存中没有才回去真正的执行数据库查询。这个时候会调用
StatefulPersistenceContext
类中的
getEntity
方法,根据
key
去获取我们真正缓存的对象。
第二次我们调用
userRepository.findById(1L);
方法的时候,跟第一次是一样的,也是会从缓存中获取数据。
JPA的源码中也是像我们开发时经常写日志的,使用
logger.debug()
什么的。所以我们可以将JPA的日志级别设置为DEBUG级别,这样我们就可以根据日志推测到JPA内部到底是怎么执行的了。
logging:level:org.hibernate.type.descriptor.sql.BasicBinder: trace
org:springframework:orm:jpa: debug
然后我们可以看到控制台的执行结果如下:
2022-08-1211:18:22.542 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.save2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT2022-08-1211:18:22.543 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(13193469<open>)] for JPA transaction2022-08-1211:18:22.555 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@2e243122]2022-08-1211:18:24.124 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(13193469<open>)] for JPA transaction2022-08-1211:18:24.124 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
Hibernate:
insertintouser(name)values(?)2022-08-1211:18:24.164 TRACE 5228--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kevin]2022-08-1211:20:03.262 WARN 5228--- [l-1 housekeeper] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Thread starvation or clock leap detected (housekeeper delta=1m7s864ms197µs100ns).
Hibernate:
insertinto
contact_info
(address, phone_number, uid)values(?, ?, ?)2022-08-1211:20:03.267 TRACE 5228--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [test address]2022-08-1211:20:03.267 TRACE 5228--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [1234]2022-08-1211:20:03.268 TRACE 5228--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [1]2022-08-1211:21:00.872 WARN 5228--- [l-1 housekeeper] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Thread starvation or clock leap detected (housekeeper delta=59s937ms26µs800ns).2022-08-1211:21:21.496 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(13193469<open>)] for JPA transaction2022-08-1211:21:21.496 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1211:23:34.497 WARN 5228--- [l-1 housekeeper] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Thread starvation or clock leap detected (housekeeper delta=2m2s354ms216µs200ns).2022-08-1211:23:36.406 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(13193469<open>)] for JPA transaction2022-08-1211:23:36.406 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1211:25:11.600 WARN 5228--- [l-1 housekeeper] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Thread starvation or clock leap detected (housekeeper delta=1m37s102ms703µs100ns).2022-08-1211:25:17.292 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2022-08-1211:25:17.292 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(13193469<open>)]2022-08-1211:25:19.667 DEBUG 5228--- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(13193469<open>)] after transaction
从执行日志可以看出整个流程如下:
- 首先是开启了事务:
Creating new transaction with name [org.example.service.UserService.save2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
- 然后是创建了一个
EntityManager
:Opened new EntityManager [SessionImpl(13193469<open>)] for JPA transaction
- 在执行完一系列操作之后,在最后提交了事务:
Committing JPA transaction on EntityManager [SessionImpl(13193469<open>)]
- 最后关闭了
EntityManager
:Closing JPA EntityManager [SessionImpl(13193469<open>)] after transaction
下面再举一个查询的例子,当我们在进行查询之后,其实也是会缓存到一级缓存里的。下面这个例子有两个事务,第一个事务是插入数据,而第二个事务是查询数据
@TransactionalpublicUsersave(){List<ContactInfo> contactInfos =newArrayList<>(List.of(ContactInfo.builder().address("test address").phoneNumber("1234").build()));User user =User.builder().name("kevin").contactInfos(contactInfos).build();return userRepository.save(user);}
@Transactional(readOnly =true)publicvoidread(){
userRepository.findById(1L);User user = userRepository.findById(1L).get();}
@TestvoidtestRead(){
userService.save();
userService.read();}
在第二个事务方法中,我们连续执行了两次
findById
操作。那最终控制台执行了几次select操作呢?是两次还是0次还是1次?
答案是1次。
一样的我们来看最终输出的执行结果,之后我们在debug看看
2022-08-1214:02:54.122 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT2022-08-1214:02:54.122 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(449653268<open>)] for JPA transaction2022-08-1214:02:54.132 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@7e305953]2022-08-1214:02:54.150 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(449653268<open>)] for JPA transaction2022-08-1214:02:54.150 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
Hibernate:
insertintouser(name)values(?)2022-08-1214:02:54.194 TRACE 22904--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kevin]
Hibernate:
insertinto
contact_info
(address, phone_number, uid)values(?, ?, ?)2022-08-1214:02:54.502 TRACE 22904--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [test address]2022-08-1214:02:54.502 TRACE 22904--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [1234]2022-08-1214:02:54.503 TRACE 22904--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]2022-08-1214:02:54.512 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2022-08-1214:02:54.512 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(449653268<open>)]2022-08-1214:02:54.557 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(449653268<open>)] after transaction2022-08-1214:02:54.558 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.read]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly2022-08-1214:02:54.558 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(1881845799<open>)] for JPA transaction2022-08-1214:02:54.564 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@1dab2cf5]2022-08-1214:02:54.564 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1881845799<open>)] for JPA transaction2022-08-1214:02:54.564 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
Hibernate:
select
user0_.id as id1_1_0_,
user0_.name as name2_1_0_
fromuser user0_
where
user0_.id=?
2022-08-1214:02:54.577 TRACE 22904--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]2022-08-1214:02:54.593 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1881845799<open>)] for JPA transaction2022-08-1214:02:54.593 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1214:02:54.594 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2022-08-1214:02:54.594 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1881845799<open>)]2022-08-1214:02:54.599 DEBUG 22904--- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(1881845799<open>)] after transaction
从日志来看确实就是查询了一次数据库。因为从日志上看我们创建的
EntityManager
,或者说我们创建的
Session
是在事务级别的,从
Opened new EntityManager [SessionImpl(449653268<open>)] for JPA transaction
这一句可以看出来。所以我们的缓存当然也是在同一个事务下才是可见的。所以在第一个事务1中虽然
save
方法缓存了数据,但是在第二个事务中我们开启了一个新的session,所以我们并看不到事务1的缓存,所以去真实的查询了数据库,并缓存了下来,然后事务2的第二个
findById
就能从缓存中获取数据,并不需要去真实的查询数据库了。
让我们可以debug来看看,建议自己动手理解更加深刻。
在第一个
findById
执行过程中会把查询后的数据进行缓存,我们可以看到会走进
StatefulPersistenceContext
类中的
addEntity
方法
在第二个
findById
执行过程中发现缓存中有对应的数据就直接拿出来,并不需要再次查询数据库了。
Hibernate的一级缓存(Session的缓存)的作用
从上面的例子来看,我们可以知道一级缓存最大的作用其实就是缓存的作用:减少访问数据库的频率。因为我们不想在代码中每次调用
find
方法,都需要真实的去查询数据库,或者是我们在保存完数据之后,我们也不需要直接去查询数据库获取数据,而是通过缓存就能获取到数据,这样可以大大减少我们对数据库的访问次数。但这仅仅只是缓存的其中一个作用,Hibernate的一级缓存还有一个作用就是用来同步缓存对象和数据库中的记录。
Hibernate的一级缓存的作用有两个
- 减少访问数据库的频率。
- 保证缓存中的对象与数据库中的相关记录保持同步。
同步缓存中的对象
前面我们都是讲了Hibernate的一级缓存中的第一个作用,现在我们来研究下它的第二个作用:保证缓存中的对象与数据库中的相关记录保持同步。
我们来举一个例子,对上面的代码做一点小小的改造
@Transactional(readOnly =true)publicvoidread(){
userRepository.findById(1L);User user = userRepository.findById(1L).get();
user.setName("test1111");}
你们觉得上面的代码,最终会执行update语句吗?答案是不会出现update语句。我们可以直接看执行日志就可以看出来,但在看之前我们需要再设置一下我们日志级别,因为我们想关注更多的信息。
logging:level:org.hibernate.type.descriptor.sql.BasicBinder: trace
org:springframework:orm:jpa: debug
hibernate:event:internal: trace
接下来我们来看执行的结果
2022-08-1215:44:47.961 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT2022-08-1215:44:47.962 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(443006127<open>)] for JPA transaction2022-08-1215:44:47.972 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@171b0d3]2022-08-1215:44:47.990 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(443006127<open>)] for JPA transaction2022-08-1215:44:47.991 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1215:44:48.000 TRACE 25408--- [ main] o.hibernate.event.internal.EntityState : Transient instance of: org.example.entity.User2022-08-1215:44:48.001 TRACE 25408--- [ main] o.h.e.i.DefaultPersistEventListener : Saving transient instance2022-08-1215:44:48.005 TRACE 25408--- [ main] o.h.e.i.AbstractSaveEventListener : Saving [org.example.entity.User#<null>]2022-08-1215:44:48.024 TRACE 25408--- [ main] o.hibernate.event.internal.WrapVisitor : Wrapped collection in role: org.example.entity.User.contactInfos
Hibernate:
insertintouser(name)values(?)2022-08-1215:44:48.042 TRACE 25408--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kevin]2022-08-1215:44:48.064 TRACE 25408--- [ main] o.hibernate.event.internal.EntityState : Transient instance of: org.example.entity.ContactInfo2022-08-1215:44:48.065 TRACE 25408--- [ main] o.h.e.i.DefaultPersistEventListener : Saving transient instance2022-08-1215:44:48.065 TRACE 25408--- [ main] o.h.e.i.AbstractSaveEventListener : Saving [org.example.entity.ContactInfo#<null>]
Hibernate:
insertinto
contact_info
(address, phone_number, uid)values(?, ?, ?)2022-08-1215:44:48.065 TRACE 25408--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [test address]2022-08-1215:44:48.065 TRACE 25408--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [1234]2022-08-1215:44:48.066 TRACE 25408--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]2022-08-1215:44:48.075 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2022-08-1215:44:48.075 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(443006127<open>)]2022-08-1215:44:48.076 TRACE 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing session2022-08-1215:44:48.076 DEBUG 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing flush-time cascades2022-08-1215:44:48.077 TRACE 25408--- [ main] o.hibernate.event.internal.EntityState : Persistent instance of: org.example.entity.ContactInfo2022-08-1215:44:48.077 TRACE 25408--- [ main] o.h.e.i.DefaultPersistEventListener : Ignoring persistent instance2022-08-1215:44:48.077 DEBUG 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Dirty checking collections2022-08-1215:44:48.078 TRACE 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing entities and processing referenced collections2022-08-1215:44:48.081 TRACE 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing unreferenced collections2022-08-1215:44:48.082 TRACE 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Scheduling collection removes/(re)creates/updates2022-08-1215:44:48.084 DEBUG 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 0 insertions, 0 updates, 0 deletions to 2 objects2022-08-1215:44:48.084 DEBUG 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 1 (re)creations, 0 updates, 0 removals to 1 collections2022-08-1215:44:48.085 TRACE 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Executing flush2022-08-1215:44:48.089 TRACE 25408--- [ main] o.h.e.i.AbstractFlushingEventListener : Post flush2022-08-1215:44:48.098 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(443006127<open>)] after transaction2022-08-1215:44:48.099 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.read]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly2022-08-1215:44:48.099 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(858872101<open>)] for JPA transaction2022-08-1215:44:48.103 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4628f386]2022-08-1215:44:48.103 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(858872101<open>)] for JPA transaction2022-08-1215:44:48.104 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1215:44:48.111 TRACE 25408--- [ main] o.h.e.internal.DefaultLoadEventListener : Loading entity: [org.example.entity.User#1]2022-08-1215:44:48.111 TRACE 25408--- [ main] o.h.e.internal.DefaultLoadEventListener : Attempting to resolve: [org.example.entity.User#1]2022-08-1215:44:48.113 TRACE 25408--- [ main] o.h.e.internal.DefaultLoadEventListener : Object not resolved in any cache: [org.example.entity.User#1]
Hibernate:
select
user0_.id as id1_1_0_,
user0_.name as name2_1_0_
fromuser user0_
where
user0_.id=?
2022-08-1215:44:48.117 TRACE 25408--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]2022-08-1215:44:48.138 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(858872101<open>)] for JPA transaction2022-08-1215:44:48.138 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1215:44:48.138 TRACE 25408--- [ main] o.h.e.internal.DefaultLoadEventListener : Loading entity: [org.example.entity.User#1]2022-08-1215:44:48.138 TRACE 25408--- [ main] o.h.e.internal.DefaultLoadEventListener : Attempting to resolve: [org.example.entity.User#1]2022-08-1215:44:48.138 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2022-08-1215:44:48.138 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(858872101<open>)]2022-08-1215:44:48.151 DEBUG 25408--- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(858872101<open>)] after transaction
这里不先讲为什么,我们先再改造一下我们的代码,然后看看执行结果:
@Transactionalpublicvoidread(){
userRepository.findById(1L);User user = userRepository.findById(1L).get();
user.setName("test1111");}
你们猜这次会有update语句吗?聪明的小伙伴肯定会说有,没错,答案就是会有update语句
我们来看一下结果
2022-08-1215:52:11.719 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT2022-08-1215:52:11.720 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(8972378<open>)] for JPA transaction2022-08-1215:52:11.732 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@6abca7a6]2022-08-1215:52:11.748 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(8972378<open>)] for JPA transaction2022-08-1215:52:11.748 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1215:52:11.761 TRACE 10668--- [ main] o.hibernate.event.internal.EntityState : Transient instance of: org.example.entity.User2022-08-1215:52:11.762 TRACE 10668--- [ main] o.h.e.i.DefaultPersistEventListener : Saving transient instance2022-08-1215:52:11.767 TRACE 10668--- [ main] o.h.e.i.AbstractSaveEventListener : Saving [org.example.entity.User#<null>]2022-08-1215:52:11.783 TRACE 10668--- [ main] o.hibernate.event.internal.WrapVisitor : Wrapped collection in role: org.example.entity.User.contactInfos
Hibernate:
insertintouser(name)values(?)2022-08-1215:52:11.801 TRACE 10668--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kevin]2022-08-1215:52:11.862 TRACE 10668--- [ main] o.hibernate.event.internal.EntityState : Transient instance of: org.example.entity.ContactInfo2022-08-1215:52:11.863 TRACE 10668--- [ main] o.h.e.i.DefaultPersistEventListener : Saving transient instance2022-08-1215:52:11.863 TRACE 10668--- [ main] o.h.e.i.AbstractSaveEventListener : Saving [org.example.entity.ContactInfo#<null>]
Hibernate:
insertinto
contact_info
(address, phone_number, uid)values(?, ?, ?)2022-08-1215:52:11.863 TRACE 10668--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [test address]2022-08-1215:52:11.863 TRACE 10668--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [1234]2022-08-1215:52:11.864 TRACE 10668--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]2022-08-1215:52:11.872 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2022-08-1215:52:11.872 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(8972378<open>)]2022-08-1215:52:11.872 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing session2022-08-1215:52:11.872 DEBUG 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing flush-time cascades2022-08-1215:52:11.874 TRACE 10668--- [ main] o.hibernate.event.internal.EntityState : Persistent instance of: org.example.entity.ContactInfo2022-08-1215:52:11.874 TRACE 10668--- [ main] o.h.e.i.DefaultPersistEventListener : Ignoring persistent instance2022-08-1215:52:11.874 DEBUG 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Dirty checking collections2022-08-1215:52:11.875 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing entities and processing referenced collections2022-08-1215:52:11.878 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing unreferenced collections2022-08-1215:52:11.878 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Scheduling collection removes/(re)creates/updates2022-08-1215:52:11.880 DEBUG 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 0 insertions, 0 updates, 0 deletions to 2 objects2022-08-1215:52:11.881 DEBUG 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 1 (re)creations, 0 updates, 0 removals to 1 collections2022-08-1215:52:11.882 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Executing flush2022-08-1215:52:11.885 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Post flush2022-08-1215:52:11.904 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(8972378<open>)] after transaction2022-08-1215:52:11.905 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.read]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT2022-08-1215:52:11.905 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(794042208<open>)] for JPA transaction2022-08-1215:52:11.908 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@1dcc0bb8]2022-08-1215:52:11.908 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(794042208<open>)] for JPA transaction2022-08-1215:52:11.909 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1215:52:11.915 TRACE 10668--- [ main] o.h.e.internal.DefaultLoadEventListener : Loading entity: [org.example.entity.User#1]2022-08-1215:52:11.915 TRACE 10668--- [ main] o.h.e.internal.DefaultLoadEventListener : Attempting to resolve: [org.example.entity.User#1]2022-08-1215:52:11.918 TRACE 10668--- [ main] o.h.e.internal.DefaultLoadEventListener : Object not resolved in any cache: [org.example.entity.User#1]
Hibernate:
select
user0_.id as id1_1_0_,
user0_.name as name2_1_0_
fromuser user0_
where
user0_.id=?
2022-08-1215:52:11.922 TRACE 10668--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]2022-08-1215:52:11.935 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(794042208<open>)] for JPA transaction2022-08-1215:52:11.936 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1215:52:11.936 TRACE 10668--- [ main] o.h.e.internal.DefaultLoadEventListener : Loading entity: [org.example.entity.User#1]2022-08-1215:52:11.936 TRACE 10668--- [ main] o.h.e.internal.DefaultLoadEventListener : Attempting to resolve: [org.example.entity.User#1]2022-08-1215:52:11.936 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2022-08-1215:52:11.936 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(794042208<open>)]2022-08-1215:52:11.936 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing session2022-08-1215:52:11.936 DEBUG 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing flush-time cascades2022-08-1215:52:11.936 DEBUG 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Dirty checking collections2022-08-1215:52:11.936 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing entities and processing referenced collections2022-08-1215:52:11.937 TRACE 10668--- [ main] o.h.e.i.DefaultFlushEntityEventListener : Found dirty properties [[org.example.entity.User#1]] : [name]2022-08-1215:52:11.937 TRACE 10668--- [ main] o.h.e.i.DefaultFlushEntityEventListener : Updating entity: [org.example.entity.User#1]2022-08-1215:52:11.937 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing unreferenced collections2022-08-1215:52:11.937 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Scheduling collection removes/(re)creates/updates2022-08-1215:52:11.937 DEBUG 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects2022-08-1215:52:11.938 DEBUG 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 0 (re)creations, 0 updates, 0 removals to 1 collections2022-08-1215:52:11.938 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Executing flush
Hibernate:
updateuserset
name=?
where
id=?
2022-08-1215:52:11.944 TRACE 10668--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [test1111]2022-08-1215:52:11.944 TRACE 10668--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [1]2022-08-1215:52:11.963 TRACE 10668--- [ main] o.h.e.i.AbstractFlushingEventListener : Post flush2022-08-1215:52:12.029 DEBUG 10668--- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(794042208<open>)] after transaction
我们可以对比一下两段代码的区别就是是否设置了
@Transactional(readOnly = true)
我们可以对比一下两个执行结果的差别,细心的小伙伴从执行结果的最后一部分就可以看到差别了。如果是设置了
@Transactional(readOnly = true)
,在最后是没有执行flush的操作,而设置了
@Transactional
会多出来flush的操作,然后在之后就会执行update的语句了。
所以通过对比上面的例子我们可以发现,看起来是否有update语句跟是否执行了flush操作其实有很大的关系
但事实真的只是因为flush操作影响的吗?我们可以在改造一下我们的代码看看,既然你说是因为flush操作影响,那我们就手动调用一下看看
@Transactional(readOnly =true)publicvoidread(){
userRepository.findById(1L);User user = userRepository.findById(1L).get();
user.setName("test1111");
userRepository.flush();}
上面的代码我们设置了
@Transactional(readOnly = true)
,同时还在最后手动调用了
userRepository.flush();
操作,接下来我们看看执行结果。
2022-08-1216:50:55.630 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT2022-08-1216:50:55.631 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(658528347<open>)] for JPA transaction2022-08-1216:50:55.641 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@321c01c2]2022-08-1216:50:55.657 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(658528347<open>)] for JPA transaction2022-08-1216:50:55.658 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1216:50:55.669 TRACE 18488--- [ main] o.hibernate.event.internal.EntityState : Transient instance of: org.example.entity.User2022-08-1216:50:55.670 TRACE 18488--- [ main] o.h.e.i.DefaultPersistEventListener : Saving transient instance2022-08-1216:50:55.672 TRACE 18488--- [ main] o.h.e.i.AbstractSaveEventListener : Saving [org.example.entity.User#<null>]2022-08-1216:50:55.690 TRACE 18488--- [ main] o.hibernate.event.internal.WrapVisitor : Wrapped collection in role: org.example.entity.User.contactInfos
Hibernate:
insertintouser(name)values(?)2022-08-1216:50:55.708 TRACE 18488--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kevin]2022-08-1216:50:55.737 TRACE 18488--- [ main] o.hibernate.event.internal.EntityState : Transient instance of: org.example.entity.ContactInfo2022-08-1216:50:55.737 TRACE 18488--- [ main] o.h.e.i.DefaultPersistEventListener : Saving transient instance2022-08-1216:50:55.737 TRACE 18488--- [ main] o.h.e.i.AbstractSaveEventListener : Saving [org.example.entity.ContactInfo#<null>]
Hibernate:
insertinto
contact_info
(address, phone_number, uid)values(?, ?, ?)2022-08-1216:50:55.737 TRACE 18488--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [test address]2022-08-1216:50:55.737 TRACE 18488--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [1234]2022-08-1216:50:55.738 TRACE 18488--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]2022-08-1216:50:55.747 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2022-08-1216:50:55.748 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(658528347<open>)]2022-08-1216:50:55.748 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing session2022-08-1216:50:55.748 DEBUG 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing flush-time cascades2022-08-1216:50:55.749 TRACE 18488--- [ main] o.hibernate.event.internal.EntityState : Persistent instance of: org.example.entity.ContactInfo2022-08-1216:50:55.750 TRACE 18488--- [ main] o.h.e.i.DefaultPersistEventListener : Ignoring persistent instance2022-08-1216:50:55.750 DEBUG 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Dirty checking collections2022-08-1216:50:55.751 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing entities and processing referenced collections2022-08-1216:50:55.755 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing unreferenced collections2022-08-1216:50:55.755 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Scheduling collection removes/(re)creates/updates2022-08-1216:50:55.758 DEBUG 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 0 insertions, 0 updates, 0 deletions to 2 objects2022-08-1216:50:55.758 DEBUG 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 1 (re)creations, 0 updates, 0 removals to 1 collections2022-08-1216:50:55.759 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Executing flush2022-08-1216:50:55.762 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Post flush2022-08-1216:50:55.770 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(658528347<open>)] after transaction2022-08-1216:50:55.771 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.read]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly2022-08-1216:50:55.771 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(1230346437<open>)] for JPA transaction2022-08-1216:50:55.774 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@624268ab]2022-08-1216:50:55.774 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1230346437<open>)] for JPA transaction2022-08-1216:50:55.775 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1216:50:55.780 TRACE 18488--- [ main] o.h.e.internal.DefaultLoadEventListener : Loading entity: [org.example.entity.User#1]2022-08-1216:50:55.780 TRACE 18488--- [ main] o.h.e.internal.DefaultLoadEventListener : Attempting to resolve: [org.example.entity.User#1]2022-08-1216:50:55.782 TRACE 18488--- [ main] o.h.e.internal.DefaultLoadEventListener : Object not resolved in any cache: [org.example.entity.User#1]
Hibernate:
select
user0_.id as id1_1_0_,
user0_.name as name2_1_0_
fromuser user0_
where
user0_.id=?
2022-08-1216:50:55.785 TRACE 18488--- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]2022-08-1216:50:55.797 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1230346437<open>)] for JPA transaction2022-08-1216:50:55.797 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1216:50:55.797 TRACE 18488--- [ main] o.h.e.internal.DefaultLoadEventListener : Loading entity: [org.example.entity.User#1]2022-08-1216:50:55.797 TRACE 18488--- [ main] o.h.e.internal.DefaultLoadEventListener : Attempting to resolve: [org.example.entity.User#1]2022-08-1216:50:55.798 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1230346437<open>)] for JPA transaction2022-08-1216:50:55.798 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction2022-08-1216:50:55.798 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing session2022-08-1216:50:55.798 DEBUG 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing flush-time cascades2022-08-1216:50:55.798 DEBUG 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Dirty checking collections2022-08-1216:50:55.798 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing entities and processing referenced collections2022-08-1216:50:55.798 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Processing unreferenced collections2022-08-1216:50:55.798 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Scheduling collection removes/(re)creates/updates2022-08-1216:50:55.798 DEBUG 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects2022-08-1216:50:55.798 DEBUG 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Flushed: 0 (re)creations, 0 updates, 0 removals to 1 collections2022-08-1216:50:55.798 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Executing flush2022-08-1216:50:55.799 TRACE 18488--- [ main] o.h.e.i.AbstractFlushingEventListener : Post flush2022-08-1216:50:55.805 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2022-08-1216:50:55.805 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1230346437<open>)]2022-08-1216:50:55.813 DEBUG 18488--- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(1230346437<open>)] after transaction
从执行结果看,我们最后也是进行了flush的操作,但是很遗憾的是并没有看到有update语句的生成。
所以从上面的测试结果看起来就是,flush操作虽然能生成update语句,但是
@Transactional(readOnly = true)
情况下不管有没有flush操作都不会有任何的update语句。
那我们对比一下这三个执行结果的区别到底在哪里?
我们可以看出来区别在于有没有执行flush,并且有没有检查出实体的改变(Dirty checking)
但我们从上面也看出来flush的时机也不一样
- 在设置了
@Transactional(readOnly = true)
不会进行自动flush操作 - 设置了
@Transactional(readOnly = false)
会在事务commit时触发flush方法,所以在图中第二部分执行结果中,Flushing session
在Initiating transaction commit
之后 - 而对于手动执行flush操作的,也就是图中第三部分执行结果中,
Flushing session
会在Flushing session 在Initiating transaction commit
之前。
Flush与事务Commit的关系
- 在当前的事务执行commit时会触发flush方法
- 在当前的事务执行完commit时,如果隔离级别是可重复度,flush之后执行的update,insert,delete的操作会被其他的新事物看到最新的结果。
- 假设当前的事务是可重复度,手动执行flush方法之后,没有执行commit,那么其他事务是看不到最新值的变化的。但最新值变化对当前没有commit的事务是有效的。
- 如果执行了flush之后,当前事务发生了rollback操作,那么数据将会被回滚(数据库的机制)
Flush的自动机制
前面讲到了当前的事务执行commit时会触发flush方法,那么除此之外还有什么情况下会自动触发flush呢?
- 事务commit之前,即指执行
transactionManager.commit()
之前都会触发 - 执行任何的JPQL或者nativeSQL(代替直接操作Entity的方法)都会触发flush(可以理解为不走一级缓存,所以这个时候拿到的可能是旧数据,所以在此之前需要把当前的最新改动flush到DB)
浅读源码解密flush方法
接下来我们就通过DEBUG的方式,来简单带大家浅读一下源码,建议自己动手试试看。
- 我们先来看看,当我们设置了
@Transactional(readOnly = true)
的时候为啥不会进行自动flush操作?
首先来看代码:
@Transactional(readOnly =true)publicvoidread(){
userRepository.findById(1L);User user = userRepository.findById(1L).get();
user.setName("test1111");}
然后我们通过DEBUG模式去运行
在transaction commit之前,也就是在调用
SessionImpl#beforeTransactionCompletion()
的时候回去判断到底需不需要去auto flush
我们可以看到这里会判断当前的
FlushMode
是不是为
MANUAL
,如果是则不会进行flush操作
我们可以详细来看看这个
FlushMode.MANUAL
,从如下的注解中我们可以看出来,
FlushMode.MANUAL
是for ready only transactions。而默认值则是
AUTO
那我们再来看看什么时候会把他设置为
MANUAL
呢?
首先我们来看到
JpaTransactionManager
的
doBegin
方法,也就是在我们开始创建事务的时候,在这个方法中我们可以找到如下调用
beginTransaction
方法的地方。
这里会调用到具体实现类
HibernateJpaDialect
的
beginTransaction
方法
在这个方法中我们就会判断我们是否设置了
readOnly=true
,如果设置了就会把
FlushMode
设置为
MANUAL
所以从上面源码的DEBUG可以看出来,当我们设置了
@Transactional(readOnly = true)
的时候,是不会自动进行flush操作的。
- 当我们
@Transactional(readOnly = true)
,但是我们在代码中手动的进行flush的操作,为啥依然没有更新DB?
一样的,我们先来看代码:
@Transactional(readOnly =true)publicvoidread(){
userRepository.findById(1L);User user = userRepository.findById(1L).get();
user.setName("test1111");
userRepository.flush();}
然后我们使用DEBUG模式来运行
当我们调用repository的flush方法的时候,最终会调用到
SessionImpl
的flush方法,然后在里面会调用到
FlushEventListener
中的
onFlush
方法,实际上调用到的是
DefaultFlushEventListener
中的
onFlush
方法。
然后就会调用到
DefaultFlushEventListener
中的
onFlush
方法,这个方法里面我们会看到调用到了
flushEverythingToExecutions
方法,然后就会调用到
AbstractFlushingEventListener
中的
flushEverythingToExecutions
方法。然后我们可以看到会调用到
flushEntities
这个方法。
然后我们在
AbstractFlushingEventListener
中的
flushEntities
方法中可以看到,调用了
onFlushEntity
方法
最后就会调用到
DefaultFlushEntityEventListener
的
onFlushEntity
方法中,在里面的
isUpdateNecessary
方法就会进行dirty checking,来判断实体是否发生了变化,如果发生了变化就要进行更新。
我们可以看到它传入了一个参数
mightBeDirty
, 我们来看这个参数是如何赋值的。我们进去
requiresDirtyCheck
方法中,然后继续看到
isModifiableEntity
方法,然后我们会发现,一旦当前的事务是read only就会返回false。
然后我们继续看
isUpdateNecessary
方法
从上面的代码可以看出来一旦当前的事务是read only的,
isUpdateNecessary
返回就是false,而且不会真正的去对比持久化对象的更新,所以也不会把更新的变化刷新到DB中。
所以到这里我们可以总结一下,为什么我们在使用 Spring 时一定要注意把查询的操作定义成只读事务?
因为一个本来只是查询的操作,却要在事务提交时进行flush操作,然后检查持久化对象的更新,看看进行了那些更新,要做这么多事情,这显然是不合理的。所以 hibernate 才给 session 的设置了这么一个
FlushMode
,那么只要这个
FlushMode
为
MANUAL
,就可以免去这些不必要的操作。所以只读事务避免了Hibernate的检测和同步持久对象的状态的更新,提升了运行性能,减少不必要的性能开销。
- 当我们设置了
@Transactional
是如何自动flush的?
@Transactionalpublicvoidread(){
userRepository.findById(1L);User user = userRepository.findById(1L).get();
user.setName("test1111");}
我们继续从上面的代码继续debug,也就是我们继续看
isUpdateNecessary
里面的代码,我们主要看里面的
dirtyCheck
方法
然后我们进到
dirtyCheck
方法中,主要看到
persister.findDirty( values, loadedState, entity, session );
这段代码,这段代码主要就是检查实体和快照中的不同。
然后我们可以看到这个方法最终走到了如下逻辑中,从代码中我们可以看到,其实是一个个字段进行比较的。
然后他就会进到
scheduleUpdate
方法中
scheduleUpdate
方法中主要就是把
EntityUpdateActon
加入到
ActionQueue
中
然后就会就会执行flush操作
可以看到其实就是把
ActionQueue
中的action取出来一个个执行,然后就向DB发起了update的语句了。
上面只是以一种update的情况下来进行DEBUG的,带大家浅读了其中一些重要的代码逻辑,感兴趣的小伙伴可以更深入的进行研究。
- Flush的时候会改变执行顺序?
如何重现这种顺序不一样的情况呢?可以参考:
JPA踩坑系列之delete(二)
spring-data-jpa踩坑 - delete-then-save唯一索引冲突问题
简单的讲就是,spring-data-jpa在一个事务中,先调用
delete
方法,再调用
save
方法时,事务提交时,并不会先执行
delete
的语句,而是直接执行
insert
语句。
那为什么不是按照我们预期的顺序,先执行
insert
然后在执行
delete
呢?
问题产生的原因,跟我们上面浅读源码看到的
ActionQueue
有关。
Hibernate 的所有操作都是以一个监听器组的形式在框架内部流转处理的,并且不会落入数据库,而是会保存到具体操作的 ActionQueue 中。ActionQueue 可以理解为具体的 SQL 操作的集合,ActionQueue 是有序的,当我们触发了 flush 事件的时候,ActionQueue 中的 SQL 才会依次落入 DB 中执行。
参考:学习笔记: JPA 与 Hibernate
我可以带大家浅读一下源码,看看它到底是按照什么顺序去触发的呢?
在上面DEBUG的过程中,我们也稍微带过了一下,我们可以看到
DefaultFlushEventListener
的
onFlush
方法,看到里面的
performExecutions
方法
在
performExecutions
方法中,我们可以看到
actionQueue.executeActions();
这句代码
在
executeActions
方法中我们可以看到,会遍历
EXECUTABLE_LISTS_MAP
,然后执行每一个action
所以我们主要关注点在于
EXECUTABLE_LISTS_MAP
里面存了什么,以及按什么顺序存的?
我们直接点进去就可以看到
EXECUTABLE_LISTS_MAP
privatestaticfinalLinkedHashMap<Class<?extendsExecutable>,ListProvider> EXECUTABLE_LISTS_MAP;static{
EXECUTABLE_LISTS_MAP =newLinkedHashMap<Class<?extendsExecutable>,ListProvider>(8);
EXECUTABLE_LISTS_MAP.put(OrphanRemovalAction.class,newListProvider<OrphanRemovalAction>(){ExecutableList<OrphanRemovalAction>get(ActionQueue instance){return instance.orphanRemovals;}ExecutableList<OrphanRemovalAction>init(ActionQueue instance){// OrphanRemovalAction executables never require sorting.return instance.orphanRemovals =newExecutableList<OrphanRemovalAction>(false);}});
EXECUTABLE_LISTS_MAP.put(AbstractEntityInsertAction.class,newListProvider<AbstractEntityInsertAction>(){ExecutableList<AbstractEntityInsertAction>get(ActionQueue instance){return instance.insertions;}ExecutableList<AbstractEntityInsertAction>init(ActionQueue instance){if( instance.isOrderInsertsEnabled()){return instance.insertions =newExecutableList<AbstractEntityInsertAction>(newInsertActionSorter());}else{return instance.insertions =newExecutableList<AbstractEntityInsertAction>(false);}}});
EXECUTABLE_LISTS_MAP.put(EntityUpdateAction.class,newListProvider<EntityUpdateAction>(){ExecutableList<EntityUpdateAction>get(ActionQueue instance){return instance.updates;}ExecutableList<EntityUpdateAction>init(ActionQueue instance){return instance.updates =newExecutableList<EntityUpdateAction>(
instance.isOrderUpdatesEnabled());}});
EXECUTABLE_LISTS_MAP.put(QueuedOperationCollectionAction.class,newListProvider<QueuedOperationCollectionAction>(){ExecutableList<QueuedOperationCollectionAction>get(ActionQueue instance){return instance.collectionQueuedOps;}ExecutableList<QueuedOperationCollectionAction>init(ActionQueue instance){return instance.collectionQueuedOps =newExecutableList<QueuedOperationCollectionAction>(
instance.isOrderUpdatesEnabled());}});
EXECUTABLE_LISTS_MAP.put(CollectionRemoveAction.class,newListProvider<CollectionRemoveAction>(){ExecutableList<CollectionRemoveAction>get(ActionQueue instance){return instance.collectionRemovals;}ExecutableList<CollectionRemoveAction>init(ActionQueue instance){return instance.collectionRemovals =newExecutableList<CollectionRemoveAction>(
instance.isOrderUpdatesEnabled());}});
EXECUTABLE_LISTS_MAP.put(CollectionUpdateAction.class,newListProvider<CollectionUpdateAction>(){ExecutableList<CollectionUpdateAction>get(ActionQueue instance){return instance.collectionUpdates;}ExecutableList<CollectionUpdateAction>init(ActionQueue instance){return instance.collectionUpdates =newExecutableList<CollectionUpdateAction>(
instance.isOrderUpdatesEnabled());}});
EXECUTABLE_LISTS_MAP.put(CollectionRecreateAction.class,newListProvider<CollectionRecreateAction>(){ExecutableList<CollectionRecreateAction>get(ActionQueue instance){return instance.collectionCreations;}ExecutableList<CollectionRecreateAction>init(ActionQueue instance){return instance.collectionCreations =newExecutableList<CollectionRecreateAction>(
instance.isOrderUpdatesEnabled());}});
EXECUTABLE_LISTS_MAP.put(EntityDeleteAction.class,newListProvider<EntityDeleteAction>(){ExecutableList<EntityDeleteAction>get(ActionQueue instance){return instance.deletions;}ExecutableList<EntityDeleteAction>init(ActionQueue instance){// EntityDeleteAction executables never require sorting.return instance.deletions =newExecutableList<EntityDeleteAction>(false);}});}
我们可以看到,其实是按照如下顺序执行的。
所以很显然,我们可以看出来
insert
是会比
delete
先执行的。
那我们如何解决这个问题呢?可以参考上面文章给出来的参考链接。
总结
JPA上手简单,让我们用操作对象的方式去操作数据库,所以实际上在框架层是做了很多东西,才让我们使用起来简单,但也因为使用不当或者不懂其中的原理让我们的代码出现了很多BUG。通过对JPA缓存机制的学习,让我们对JPA的实现原理有更深一步的认识。也希望通过本篇文章让大家对JPA的缓存机制有一定的了解,也帮助大家如何去DEBUG和阅读JPA底层的源码。JPA底层里面做了很多东西,包括我也没有全部了解,也正在学习的过程中,所以一篇文章没法带大家学完全部的内容,希望感兴趣的小伙伴可以更加深入的进行学习,如果有任何问题,欢迎指出,谢谢!
参考
详谈hibernate,jpa与spring data jpa三者之间的关系
比较JPA的EntityManager接口与Hibernate的Session接口
What is the difference between a Session and a Connection in Hibernate?
Hibernate 深入Session
Spring Data JPA的使用踩坑:关于缓存与快照
Spring data jpa 缓存机制总结
SPRING DATA JPA 缓存机制总结
Spring Boot JPA 常见的那些坑
Hibernate 复习笔记 (3)——Session 缓存(Hibernate 一级缓存)详解
Hibernate 一级缓存导致某保险公司核心系统Weblogic中间件内存溢出的故障诊断
Hibernate缓存策略
JPA Session 一劳永逸
JPA 查询问题探究
JPA最佳实践
JPA踩坑系列
Hibernate 二级缓存
源代码解读Spring只读事务与读写事务的性能的差别
主题:解惑:在spring+hibernate中,只读事务是如何被优化的
主题:Spring声明式事务管理源码解读之事务开始
主题:Spring声明式事务管理源码解读之事务提交
Spring Data JPA 原理与实战第十一天 Session相关、CompletableFuture、LazyInitializationException
Spring Data JPA 视频
学习笔记: JPA 与 Hibernate
Spring Data JPA 原理与实战第十一天 JPAS事务、Hibernate和Persistence Context
spring-data-jpa踩坑 - delete-then-save唯一索引冲突问题
版权归原作者 c. 所有, 如有侵权,请联系我们删除。