Hibernate JPA 缓存配置
1、一级缓存
一级缓存指的是 EntityManager 级的缓存,对于这样的缓存几乎是一直存在的,也就是说只要用户进行JPA的操作,那么就永远都会存在有一级缓存
新建数据库视图类和初始化数据
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "tb_cache")
public class CacheEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private String name;
private int age;
}
@Before
public void initData(){
EntityManager entityManager = JpaUtils.getEntityManager();
entityManager.getTransaction().begin();
Arrays.asList(
CacheEntity.builder().name("Sam").age(18).build(),
CacheEntity.builder().name("Mike").age(58).build(),
CacheEntity.builder().name("Hom").age(48).build(),
CacheEntity.builder().name("Nick").age(28).build(),
CacheEntity.builder().name("Kath").age(28).build()
).forEach(cacheEntity -> entityManager.persist(cacheEntity));
entityManager.getTransaction().commit();
}
1、使用 fnd() 执行两次查询,并且查询的ID信息都一样:
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFindCache(){
EntityManager entityManager = JpaUtils.getEntityManager();
CacheEntity cacheEntity1 = entityManager.find(CacheEntity.class, 1L);
System.err.println(cacheEntity1);
System.err.println("--------------华丽的分割线-------------------");
CacheEntity cacheEntity2 = entityManager.find(CacheEntity.class, 1L);
System.err.println(cacheEntity2);
}
查看日志:
Hibernate:
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=1, name=Sam, age=18)
--------------华丽的分割线-------------------
CacheEntity(Id=1, name=Sam, age=18) // 可以发现第二次查询的时候没有发送SQL语句
PS:可以发现这个时候真正执行的是一次数据库查询,也就是说在同一个EntityManager
对象操作之中如果使用 find() 查询,那么同一条数据默认情况下只会査询一次,查询完成之后会将这个数据进行缓存。
2、如果说现在在查询过程之中发生了内容的变更呢?如:第一次查询出来的对象现在要求进行一些修改。
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFindCache2(){
EntityManager entityManager = JpaUtils.getEntityManager();
CacheEntity cacheEntity1 = entityManager.find(CacheEntity.class, 1L);
cacheEntity1.setName("SamXX"); // 第一次查询的数据发生改变
System.err.println(cacheEntity1);
System.err.println("--------------华丽的分割线-------------------");
CacheEntity cacheEntity2 = entityManager.find(CacheEntity.class, 1L);
System.err.println(cacheEntity2);
}
查看日志:
Hibernate:
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=1, name=SamXX, age=18)
--------------华丽的分割线-------------------
CacheEntity(Id=1, name=SamXX, age=18) // 可以发现第二次查询的时候可以发送SQL语句
PS:第一个数据读取进来之后由于发生了改变,肯定和原始数据库的内容不一样了,那么此时通过第二次查询获得的结果可以发现,依然只是查询了一次,并且在ID不改变的情况下,会发生相同ID的查询依然引用已有的缓存对象.
3、如果现在想内容进行更新,需要采用刷新的模式完成。entityManager.refresh(object)
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFindRefresh(){
EntityManager entityManager = JpaUtils.getEntityManager();
CacheEntity cacheEntity1 = entityManager.find(CacheEntity.class, 1L);
cacheEntity1.setName("SamXX"); // 第一次查询的数据发生改变
System.err.println(cacheEntity1);
entityManager.refresh(cacheEntity1); // 重新加载,会再次发送SQL
System.err.println("--------------华丽的分割线-------------------");
CacheEntity cacheEntity2 = entityManager.find(CacheEntity.class, 1L);
System.err.println(cacheEntity2);
}
查看日志:
Hibernate:
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=1, name=SamXX, age=18)
Hibernate:
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
--------------华丽的分割线-------------------
CacheEntity(Id=1, name=Sam, age=18)
此时同样操作发现重复查询了两次,主要是因为使用了refresh()
明确表示该缓存对象需要重新加载。在JPA
中有一个非常重要的概念:JPA
的对象状态,在JPA
里面一共有四种对象状态.
2、对象状态(生命周期)
参考文献 & 鸣谢:JPA实体的四种状态:https://www.jianshu.com/p/636954880af8
JPA 对象四种状态(老版中只有三种,没有移除态):
- 瞬时态对象(瞬时态:New or Transient):尚未有id,还未和 Persistence Context 建立关联的对象
- 持久态对象(持久态:Managed or Persistent):有id值,与 Persistence Context 建立了关联的对象
- 游离态对象(游离态:Datached):有id值,没有和 Persistence Context 建立关联的对象
- 删除的对象(移除态:Removed):有id值,尚未和 Persistence Context 有关联,但是已经准备好从数据库中删除(确切的说在事物提交前还与 Persistence Context 有关联,事物提交后就与 Persistence Context 没有关联了)
- 临时状态/瞬时状态(transient):没有与 entityManager 发生关系,没有被持久化,不处于 entityManager 中的对象
- 持久化状态/托管状态(persistent):与 entityManager 发生关系,已经被持久化,加入到 entityManager 的一级缓存中的对象
- 删除状态(removed):调用了 entityManager.remove(obj),对象有关联的ID,并且在 entityManager 管理下。调用remove方法后已经计划删除,事物提交后才会被真正从数据库删除
- 游离状态(detached):脱管状态:对象和 entityManager 解除关系
持久状态
| ⬆
find() flush()
⬇ |
New POJO() ----> 瞬时状态 ----persist()----> 托管状态 ----remove()----> 销毁状态
| ⬆
事物提交 meger/refresh
⬇ |
游离状态
1、示例说明:
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFindAdd(){
CacheEntity cacheEntity = new CacheEntity(); // 临时状态
cacheEntity.setName("lsx");
cacheEntity.setAge(16);
EntityManager entityManager = JpaUtils.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(cacheEntity); // 持久状态
entityManager.remove(cacheEntity); // 删除状态
entityManager.getTransaction().commit();// 提交更新事务
entityManager.close();
System.err.println(cacheEntity);// 游离状态
}
2、观察在持久态下的数据更新(重点)
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFindEdit(){
EntityManager entityManager = JpaUtils.getEntityManager();
// 持久态
CacheEntity cacheEntity = entityManager.find(CacheEntity.class, 1L);
entityManager.getTransaction().begin(); // 开启事务
cacheEntity.setName("SamXX"); // 持久化数据发生改变
entityManager.getTransaction().commit(); // 提交更新事务(发现我们并没有提交更新操作)
entityManager.close();
}
查看日志:
Hibernate: // entityManager.find(CacheEntity.class, 1L);
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
Hibernate: // 更新了持久态中的属性内容,并且提交了事务
update
tb_cache
set
age=?,
name=?
where
Id=?
-- 然后查看数据库这条数据:
mysql> select * from tb_cache where id = 1;
+----+-----+-------+
| Id | age | name |
+----+-----+-------+
| 1 | 18 | SamXX |
+----+-----+-------+
1 row in set (0.02 sec)
PS:发现这条数据name发生了改变,但是我们并没有执行更新方法,可以得出结论:持久态话下的数据发生改变的话,只要在事务中并且提交了事务更新,那么默认就相当于执行了更新数据库操作
3、一个新的对象默认情况下属于瞬时态,瞬时态也可以持久化
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFindAdd2(){
CacheEntity cacheEntity = new CacheEntity(); // 临时状态
cacheEntity.setName("lsx");
cacheEntity.setAge(16);
EntityManager entityManager = JpaUtils.getEntityManager();
entityManager.getTransaction().begin();// 开启事务
entityManager.persist(cacheEntity);// 数据持久化,自动可以获取ID
entityManager.getTransaction().commit();// 提交更新事务
System.err.println("-----新增数据的ID是:" + cacheEntity.getId());
// 将刚刚保存对象信息根据ID查询出来
CacheEntity selectCacheEntity = entityManager.find(CacheEntity.class, cacheEntity.getId());
System.err.println(selectCacheEntity);
entityManager.close();
}
查看日志:
Hibernate:
insert
into
tb_cache
(age, name)
values
(?, ?)
-----新增数据的ID是:6
CacheEntity(Id=6, name=lsx, age=16) // 可以发现查询数据没有发送SQL,而是从缓存中获取
PS:由于现在使用了persisit()
方法将瞬时态对象变为了持久态,所以这个对象就会被缓存起来,那么再执行一次查询的时候就不会重复发出查询命令,而是直接使用缓存中的数据.
4、如果每新增一个数据都被缓存起来,当批量新增时,缓存所占用的空间就会出现严重的不足。最好的做法是进行:强制性的保存以及清空
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFindFlushAndClear(){
CacheEntity cacheEntity = new CacheEntity();
cacheEntity.setName("lsx");
cacheEntity.setAge(16);
EntityManager entityManager = JpaUtils.getEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(cacheEntity);// 数据持久化,自动可以获取ID
entityManager.flush(); // 强制立即写入数据库
entityManager.clear(); // 清空缓存
entityManager.getTransaction().commit();// 提交更新事务
System.err.println("-----新增数据的ID是:" + cacheEntity.getId());
// 将刚刚保存对象信息根据ID查询出来
CacheEntity selectCacheEntity = entityManager.find(CacheEntity.class, cacheEntity.getId());
System.err.println(selectCacheEntity);
entityManager.close();
}
查看日志:
Hibernate:
insert
into
tb_cache
(age, name)
values
(?, ?)
-----新增数据的ID是:6
Hibernate:
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=6, name=lsx, age=16)
PS:由于清除了缓存,所以此时的数据再次查询的时候就需要重新发出查询指令,当然重新查询之后也就意味这一个对象重新被缓存了
所以在真实进行数据的批量增加时,我们应该适当加上强制写入和清空缓存(例如每新增10条数据后就执行一次flush
和clear
方法)
5、删除数据(游离状态)
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testDelete(){
EntityManager entityManager = JpaUtils.getEntityManager();
CacheEntity cacheEntity = entityManager.find(CacheEntity.class, 1L);
entityManager.getTransaction().begin();// 开启事务
entityManager.remove(cacheEntity); // 删除数据
cacheEntity.setName("小明");
entityManager.getTransaction().commit();// 提交事务
entityManager.close();
}
查看日志:
Hibernate:
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
Hibernate:
delete
from
tb_cache
where
Id=?
PS:删除的数据就属于游离态了,所以此时已无法实现试据的持久态管理了
3、二级缓存
一级缓存时针对与EntityManager
的缓存处理,并且永久存在,而二级缓存指的是针对于多个EntityManager
实现的缓存处理,但是二级缓存默认是没有开启的
1、建立两个不同的 EntityManager 进行数据查询
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFind(){
EntityManager entityManagerA = JpaUtils.getEntityManager();
System.err.println(entityManagerA.find(CacheEntity.class, 1L));
entityManagerA.close();
System.err.println("--------------华丽的分割线-------------------");
EntityManager entityManagerB = JpaUtils.getEntityManager();
System.err.println(entityManagerB.find(CacheEntity.class, 1L));
entityManagerB.close();
}
查看日志:
Hibernate:
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=1, name=Sam, age=18)
--------------华丽的分割线-------------------
Hibernate:
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=1, name=Sam, age=18)
可以发现不同的Seeson(EntityManager)查询同一条数据的时候依然发出了两次查询指令,所以此时表示JPA中没有开启二级缓存
2、二级缓存一般用第三方组件:Redis、Ehache,这里用Ehcache
为例,pox.xml引入Ehcache
依赖
<!--添加Hibernate-Ehcache包,版本号与hibernate一样 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.4.1.Final</version>
</dependency>
<!--Ehcache-core 包 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
3、在resources
目录下新建一个**ehcache.xml
**文件。如果在加载时未找到/ehcache.xml
资源或出现问题,则将使用默认配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
- user.home – 用户主目录
- user.dir – 用户当前工作目录
- java.io.tmpdir – 默认临时文件路径
- 或自定义一个本地磁盘路径例如:/home、./tmpdir/Tmp_EhCache
-->
<!-- 磁盘缓存位置 -->
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
</ehcache>
4、在JPA配置文件persistence.xml
中增加二级缓存配置
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!-- 持久化单元:持久化单元事务类型,RESOURCE_LOCAL:本地事务管理 -->
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!--
配置二级缓存时候使用的模式,可配置值有:
- ALL:所有的实体类都被缓存
- NONE:所有的实体类都不被缓存
- ENABLE_SELECTIVE:标识@Cacheable(true)注解的实体类将被缓存
- DISABLE_SELECTIVE;缓存除标识@Cacheable(false)以外的所有实体类
- UNSPECIFIED:默认值,JPA 产品默认值将被使用
-->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<!--可选配置:配置jpa实现方的配置信息-->
<properties>
<!-- 数据库信息配置:数据库驱动、数据库地址、数据库账户、数据库密码 -->
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.url" value="jdbc:mysql://127.0.0.1:3306/hibernate_jpa"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="password"/>
<!-- 配置JPA服务提供商可选参数 -->
<property name="hibernate.show_sql" value="true" /><!-- 自动显示sql -->
<property name="hibernate.format_sql" value="true"/><!-- 格式化sql -->
<property name="hibernate.hbm2ddl.auto" value="update" /><!-- 自动建表:none,create,update,create-drop,validate -->
<!-- 二级缓存相关 -->
<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<!-- 配置二级缓存处理类 -->
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
<!-- 开启查询缓存,entityManager.find查询可以不配置,如果使用JPQL或SQL查询需要开启该配置 -->
<property name="hibernate.cache.use_query_cache" value="true"/>
<!-- 指定缓存配置文件位置,如果默认在resources下可不配置 -->
<property name="hibernate.cache.provider_configuration" value="classpath:ehcache.xml"/>
</properties>
</persistence-unit>
</persistence>
5、其中ENABLE_SELECTIVE
模式为实体类上配置 @Cacheable(true)
的才会进行生效
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "tb_cache")
@Cacheable(value = true) // 增加此注解即可,value默认为true,直接配置@Cacheable也可以
public class CacheEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private String name;
private int age;
}
/***************另一种用法(没有特殊需求只用上面配置即可)******************/
/**
* 如果只配置@Cacheable的话是用ehcache.xml中默认defaultCache配置,
* 想用自定义名称的缓存配置,需要增加@Cache注解,例如想使用ehcache.xml中的<cache name="cloud_user"../> 缓存配置
* 增加注解 @Cache(usage = CacheConcurrencyStrategy.READ_WRITE,region="指定的cache name")
**/
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE,region="cloud_user")
@Cacheable(value = true)
@Entity
@Table(name = "tb_cache")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CacheEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private String name;
private int age;
}
usage提供缓存对象的事务隔离机制有这几种:NONE、READ_ONLY、 NONSTRICT_READ_WRITE、READ_WRITE、TRANSACTIONAL
- NONE:默认什么都不做
- READ_ONLY:只读模式,在此模式下,如果对数据进行更新操作,会有异常
- READ_WRITE:读写模式在更新缓存的时候会把缓存里面的数据换成一个锁,其它事务如果去取相应的缓存数据,发现被锁了,直接就去数据库查询
- NONSTRICT_READ_WRITE:不严格的读写模式则不会的缓存数据加锁
- TRANSACTIONAL:事务模式指缓存支持事务,当事务回滚时,缓存也能回滚,只支持JTA环境。
Ehcache不支持transaction事务机制,但其他三种可以使用:
- read-only:无需修改, 那么就可以对其进行只读 缓存,注意,在此策略下,如果直接修改数据库,即使能够看到前台显示效果,但是将对象修改至cache中会报error,cache不会发生作用。另:删除记录会报错,因为不能在read-only模式的对象从cache中删除
- read-write:需要更新数据,那么使用读/写缓存 比较合适,前提:数据库不可以为serializable transaction isolation level(序列化事务隔离级别)
- nonstrice-read-write:只偶尔需要更新数据(也就是说,两个事务同时更新同一记录的情况很不常见),也不需要十分严格的事务隔离,那么比较适合使用非严格读/写缓存策略。
6、再次测试Test代码:
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFind(){
EntityManager entityManagerA = JpaUtils.getEntityManager();
System.err.println(entityManagerA.find(CacheEntity.class, 1L));
entityManagerA.close();
System.err.println("--------------华丽的分割线-------------------");
EntityManager entityManagerB = JpaUtils.getEntityManager();
System.err.println(entityManagerB.find(CacheEntity.class, 1L));
entityManagerB.close();
}
查看日志:
Hibernate:
select
cacheentit0_.Id as Id1_2_0_,
cacheentit0_.age as age2_2_0_,
cacheentit0_.name as name3_2_0_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=1, name=Sam, age=18)
--------------华丽的分割线-------------------
CacheEntity(Id=1, name=Sam, age=18) // 可以发现第二次查询没有发送查询语句了
3、查询缓存
在进行数据查询都是直接EntityManager
的方法直接查询,这种方式可以帮助用户直接进行缓存处理,如果现在使用Query
查询,则对于缓存配置就不生效了
PS:而使用Query
查询是没有缓存的,需要设置:query.setHint(QueryHints.HINT_CACHEABLE, true);
(且每新创建一个Query
都要设置)
由于在二级缓存中已经配置了如下配置,我们先注释掉下面的配置或者关闭配置开始测试:
<!-- 开启查询缓存,entityManager.find查询可以不配置,如果使用JPQL或SQL查询需要开启该配置,并设置query.setHint() -->
<!--<property name="hibernate.cache.use_query_cache" value="true"/>-->
<property name="hibernate.cache.use_query_cache" value="false"/>
1、观察默认情况下Query
查询处理(分别测试开启查询缓存和关闭查询缓存)
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFindQuery(){
String jpql = "from CacheEntity where Id = :Id";
EntityManager entityManagerA = JpaUtils.getEntityManager();
TypedQuery<CacheEntity> queryA = entityManagerA.createQuery(jpql, CacheEntity.class);
queryA.setParameter("Id", 1L);
System.err.println(queryA.getSingleResult());
entityManagerA.close();
System.err.println("--------------华丽的分割线-------------------");
EntityManager entityManagerB = JpaUtils.getEntityManager();
TypedQuery<CacheEntity> queryB = entityManagerB.createQuery(jpql, CacheEntity.class);
queryB.setParameter("Id", 1L);
System.err.println(queryB.getSingleResult());
entityManagerB.close();
}
查看日志:
Hibernate:
select
cacheentit0_.Id as Id1_2_,
cacheentit0_.age as age2_2_,
cacheentit0_.name as name3_2_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=1, name=Sam, age=18)
--------------华丽的分割线-------------------
Hibernate:
select
cacheentit0_.Id as Id1_2_,
cacheentit0_.age as age2_2_,
cacheentit0_.name as name3_2_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=1, name=Sam, age=18)
PS:发现默认情况下即便开启或者固安必二级查询缓存,对于Query查询也是无效的,所以还是需要开启查询缓存配置(后面需要加一个配置才能生效)
2、开启查询缓存配置(还原注释掉的配置),使用QueryHints
操作
// 运行之前,修改hibernate.hbm2ddl.auto=create
@Test
public void testFindQuery2(){
String jpql = "from CacheEntity where Id = :Id";
EntityManager entityManagerA = JpaUtils.getEntityManager();
TypedQuery<CacheEntity> queryA = entityManagerA.createQuery(jpql, CacheEntity.class);
queryA.setHint(QueryHints.HINT_CACHEABLE,true);
queryA.setParameter("Id", 2L);
System.err.println(queryA.getSingleResult());
entityManagerA.close();
System.err.println("--------------华丽的分割线-------------------");
EntityManager entityManagerB = JpaUtils.getEntityManager();
TypedQuery<CacheEntity> queryB = entityManagerB.createQuery(jpql, CacheEntity.class);
queryB.setHint(QueryHints.HINT_CACHEABLE,true);
queryB.setParameter("Id", 2L);
System.err.println(queryB.getSingleResult());
entityManagerB.close();
}
查看日志:
Hibernate:
select
cacheentit0_.Id as Id1_2_,
cacheentit0_.age as age2_2_,
cacheentit0_.name as name3_2_
from
tb_cache cacheentit0_
where
cacheentit0_.Id=?
CacheEntity(Id=2, name=Mike, age=58)
--------------华丽的分割线-------------------
CacheEntity(Id=2, name=Mike, age=58) // 可以发现只发送了一次查询操作
PS:使用Query
查询是没有缓存的,需要设置:query.setHint(QueryHints.HINT_CACHEABLE, true);
(且每新创建一个Query
都要设置)