Spring Data JPA想要学得好,缓存机制掌握好
Posted c.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Data JPA想要学得好,缓存机制掌握好相关的知识,希望对你有一定的参考价值。
文章目录
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的一级缓存,所以了解缓存机制就变得尤为重要,下面举一个例子来看看:
@Transactional
public User test()
List<ContactInfo> contactInfos = new ArrayList<>(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-12 11:18:22.542 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.save2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-08-12 11:18:22.543 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(13193469<open>)] for JPA transaction
2022-08-12 11: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-12 11:18:24.124 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(13193469<open>)] for JPA transaction
2022-08-12 11:18:24.124 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
Hibernate:
insert
into
user
(name)
values
(?)
2022-08-12 11:18:24.164 TRACE 5228 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kevin]
2022-08-12 11: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:
insert
into
contact_info
(address, phone_number, uid)
values
(?, ?, ?)
2022-08-12 11:20:03.267 TRACE 5228 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [test address]
2022-08-12 11:20:03.267 TRACE 5228 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [1234]
2022-08-12 11:20:03.268 TRACE 5228 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [1]
2022-08-12 11: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-12 11:21:21.496 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(13193469<open>)] for JPA transaction
2022-08-12 11:21:21.496 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2022-08-12 11: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-12 11:23:36.406 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(13193469<open>)] for JPA transaction
2022-08-12 11:23:36.406 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2022-08-12 11: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-12 11:25:17.292 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2022-08-12 11:25:17.292 DEBUG 5228 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(13193469<open>)]
2022-08-12 11: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
下面再举一个查询的例子,当我们在进行查询之后,其实也是会缓存到一级缓存里的。下面这个例子有两个事务,第一个事务是插入数据,而第二个事务是查询数据
@Transactional
public User save()
List<ContactInfo> contactInfos = new ArrayList<>(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)
public void read()
userRepository.findById(1L);
User user = userRepository.findById(1L).get();
@Test
void testRead()
userService.save();
userService.read();
在第二个事务方法中,我们连续执行了两次findById
操作。那最终控制台执行了几次select操作呢?是两次还是0次还是1次?
答案是1次。
一样的我们来看最终输出的执行结果,之后我们在debug看看
2022-08-12 14:02:54.122 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-08-12 14:02:54.122 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(449653268<open>)] for JPA transaction
2022-08-12 14: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-12 14:02:54.150 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(449653268<open>)] for JPA transaction
2022-08-12 14:02:54.150 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
Hibernate:
insert
into
user
(name)
values
(?)
2022-08-12 14:02:54.194 TRACE 22904 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kevin]
Hibernate:
insert
into
contact_info
(address, phone_number, uid)
values
(?, ?, ?)
2022-08-12 14:02:54.502 TRACE 22904 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [test address]
2022-08-12 14:02:54.502 TRACE 22904 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [1234]
2022-08-12 14:02:54.503 TRACE 22904 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]
2022-08-12 14:02:54.512 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2022-08-12 14:02:54.512 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(449653268<open>)]
2022-08-12 14:02:54.557 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(449653268<open>)] after transaction
2022-08-12 14: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,readOnly
2022-08-12 14:02:54.558 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(1881845799<open>)] for JPA transaction
2022-08-12 14: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-12 14:02:54.564 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1881845799<open>)] for JPA transaction
2022-08-12 14: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_
from
user user0_
where
user0_.id=?
2022-08-12 14:02:54.577 TRACE 22904 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
2022-08-12 14:02:54.593 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1881845799<open>)] for JPA transaction
2022-08-12 14:02:54.593 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2022-08-12 14:02:54.594 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2022-08-12 14:02:54.594 DEBUG 22904 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1881845799<open>)]
2022-08-12 14: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)
public void read()
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-12 15:44:47.961 DEBUG 25408 --- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.example.service.UserService.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-08-12 15:44:47.962 DEBUG 25408 --- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(443006127<open>)] for JPA transaction
2022-08-12 15: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-12 15:44:47.990 DEBUG 25408 --- [ main] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(443006127<open>)] for JPA transaction
2022-08-12 15:44:47.991 DEBUG 25408 --- [ main] o.s.orm.jpa.JpaTransactionManager : Participating in existing transaction
2022-08-12 15:44:48.000 TRACE 25408 --- [ main] o.hibernate.event.internal.EntityState : Transient instance of: org.example.entity.User
2022-08-12 15:44:48.001 TRACE 25408 --- [ main] o.h.e.i.DefaultPersistEventListener : Saving transient instance
2022-08-12 15:44:48.005 TRACE 25408 --- [ main] o.h.e.i.AbstractSaveEventListener : Saving [org.example.entity.User#<null>]
2022-08-12 15:44:48.024 TRACE 25408 --- [ main] o.hibernate.event.internal.WrapVisitor : Wrapped collection in role: org.example.entity.User.contactInfos
Hibernate:
insert
into
user
(name)
values
(?)
2022-08-12 15:44:48.042 TRACE 25408 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [kevin]
2022-08-12 15:44:48.064 TRACE 25408 --- [ main] o.hibernate.event.internal.EntityState : Transient instance of: org.example.entity.ContactInfo
2022-08-12 15:44:48.065 TRACE 25408 --- [ main] o.h.e.i.DefaultPersistEventListener : Saving transient instance
2022-08-12 15:44:48.065 TRACE 25408 --- [ main] o.h.e.i.AbstractSaveEventListener : Saving [org.example.entity.ContactInfo#<null>]
Hibernate:
insert
into
contact_info
(address, phone_number, uid)
values
(?, ?, ?)
2022-08-12 15:44:48.065 TRACE 25408 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [test address]
2022-08-12 15:44:48.065 TRACE 25408 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [VARCHAR] - [1234]
2022-08-12 15:44:48.066 TRACE 25408 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [null]
2022-08-12 15:44:48.075 DEBUG 25408 --- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit
2022-08-12 15:44:48.075 DEBUG 25408 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(443006127<open>)]
2022-08-12 15:44:48.076 TRACE 25408 --- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing session
2022-08-12 15:44:48.076 DEBUG 25408 --- [ main] o.h.e.i.AbstractFlushingEventListener : Processing flush-time cascades
2022-08-12 15:44:48.077 TRACE 25408 --- [ main] o.hibernate.event.internal.EntityState : Persistent instance of: org.example.entity.ContactInfo
2022-08-12 15:44:48.077 TRACE 25408 --- [ main] o.h.e.i.DefaultPersistEventListener : Ignoring persistent instance
2022-08-12 15:44:48.077 DEBUG 25408 --- [ main] o.h.e.i.AbstractFlushingEventListener : Dirty checking collections
2022-08-12 15:44:48.078 TRACE 25408 --- [ main] o.h.e.i.AbstractFlushingEventListener : Flushing entities and processing referenced collections
2022-08-12 15:44:48.081 TRACE 25408 --- [ main] o.h.e.i.AbstractFlushingEventListener : Processing unreferenced collections
2022-08-12 15:44:48.082 TRACE 25408 --- [ main] o.h.e.i.AbstractFlushingEventListener : Scheduling collection removes/(re)creates/updates
2022-08-12 15:Spring data jpa删除元素时必须先查询出来吗