介绍

之前,我描述了Hibernate用于存储实体的二级缓存条目结构。 除了实体,Hibernate还可以存储实体关联,本文将阐明集合缓存的内部工作原理。

领域模型

对于即将进行的测试,我们将使用以下实体模型:

存储库具有一组Commit实体:

@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE
)
@OneToMany(mappedBy = "repository", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Commit> commits = new ArrayList<>();

每个Commit实体都有一组Change可嵌入元素。

@ElementCollection
@CollectionTable(name="commit_change",joinColumns = @JoinColumn(name="commit_id")
)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE
)
@OrderColumn(name = "index_id")
private List<Change> changes = new ArrayList<>();

现在,我们将插入一些测试数据:

doInTransaction(session -> {Repository repository = new Repository("Hibernate-Master-Class");session.persist(repository);Commit commit1 = new Commit();commit1.getChanges().add(new Change("README.txt", "0a1,5..."));commit1.getChanges().add(new Change("web.xml", "17c17..."));Commit commit2 = new Commit();commit2.getChanges().add(new Change("README.txt", "0b2,5..."));repository.addCommit(commit1);repository.addCommit(commit2);session.persist(commit1);
});

直读缓存

集合缓存采用了一种通读同步策略 :

doInTransaction(session -> {Repository repository = (Repository) session.get(Repository.class, 1L);for (Commit commit : repository.getCommits()) {assertFalse(commit.getChanges().isEmpty());}
});

并且首次访问集合时将对其进行缓存:

selectcollection0_.id as id1_0_0_,collection0_.name as name2_0_0_
fromRepository collection0_
wherecollection0_.id=1  selectcommits0_.repository_id as reposito3_0_0_,commits0_.id as id1_1_0_,commits0_.id as id1_1_1_,commits0_.repository_id as reposito3_1_1_,commits0_.review as review2_1_1_
fromcommit commits0_
wherecommits0_.r  selectchanges0_.commit_id as commit_i1_1_0_,changes0_.diff as diff2_2_0_,changes0_.path as path3_2_0_,changes0_.index_id as index_id4_0_
fromcommit_change changes0_
wherechanges0_.commit_id=1  selectchanges0_.commit_id as commit_i1_1_0_,changes0_.diff as diff2_2_0_,changes0_.path as path3_2_0_,changes0_.index_id as index_id4_0_
fromcommit_change changes0_
wherechanges0_.commit_id=2

在缓存存储库及其关联的提交之后,由于所有实体及其关联都由第二级缓存提供服务,因此加载存储库并遍历“ 提交更改”集合将不会访问数据库:

LOGGER.info("Load collections from cache");
doInTransaction(session -> {Repository repository = (Repository) session.get(Repository.class, 1L);assertEquals(2, repository.getCommits().size());
});

运行先前的测试用例时,没有执行SQL SELECT语句:

CollectionCacheTest - Load collections from cache
JdbcTransaction - committed JDBC Connection

集合缓存条目结构

对于实体集合,Hibernate仅存储实体标识符,因此也需要缓存实体:

key = {org.hibernate.cache.spi.CacheKey@3981}key = {java.lang.Long@3597} "1"type = {org.hibernate.type.LongType@3598} entityOrRoleName = {java.lang.String@3599} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.CollectionCacheTest$Repository.commits"tenantId = nullhashCode = 31
value = {org.hibernate.cache.ehcache.internal.strategy.AbstractReadWriteEhcacheAccessStrategy$Item@3982} value = {org.hibernate.cache.spi.entry.CollectionCacheEntry@3986} "CollectionCacheEntry[1,2]"version = nulltimestamp = 5858841154416640

CollectionCacheEntry存储与给定存储库实体关联的提交标识符。

由于元素类型没有标识符,因此Hibernate会存储其脱水状态。 更改可嵌入的内容缓存如下:

key = {org.hibernate.cache.spi.CacheKey@3970} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.CollectionCacheTest$Commit.changes#1"key = {java.lang.Long@3974} "1"type = {org.hibernate.type.LongType@3975} entityOrRoleName = {java.lang.String@3976} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.CollectionCacheTest$Commit.changes"tenantId = nullhashCode = 31
value = {org.hibernate.cache.ehcache.internal.strategy.AbstractReadWriteEhcacheAccessStrategy$Item@3971} value = {org.hibernate.cache.spi.entry.CollectionCacheEntry@3978}state = {java.io.Serializable[2]@3980} 0 = {java.lang.Object[2]@3981} 0 = {java.lang.String@3985} "0a1,5..."1 = {java.lang.String@3986} "README.txt"1 = {java.lang.Object[2]@3982} 0 = {java.lang.String@3983} "17c17..."1 = {java.lang.String@3984} "web.xml"version = nulltimestamp = 5858843026345984

集合缓存一致性模型

在使用缓存时 ,一致性是最大的问题 ,因此我们需要了解Hibernate Collection Cache如何处理实体状态更改。

CollectionUpdateAction负责所有Collection的修改,并且只要集合发生更改,就会将关联的缓存条目逐出:

protected final void evict() throws CacheException {if ( persister.hasCache() ) {final CacheKey ck = session.generateCacheKey(key, persister.getKeyType(), persister.getRole());persister.getCacheAccessStrategy().remove( ck );}
}

CollectionRegionAccessStrategy规范也记录了此行为:

对于缓存的收集数据,所有修改操作实际上只会使条目无效。

根据当前的并发策略,收回集合缓存条目:

  • 在提交当前事务之前 ,用于CacheConcurrencyStrategy.NONSTRICT_READ_WRITE
  • 立即提交当前事务 ,用于CacheConcurrencyStrategy.READ_WRITE
  • 对于CacheConcurrencyStrategy.TRANSACTIONAL , 确切地在何时提交当前事务

添加新的收藏夹条目

以下测试案例向我们的存储库添加了一个新的Commit实体:

LOGGER.info("Adding invalidates Collection Cache");
doInTransaction(session -> {Repository repository = (Repository) session.get(Repository.class, 1L);assertEquals(2, repository.getCommits().size());Commit commit = new Commit();commit.getChanges().add(new Change("Main.java", "0b3,17..."));repository.addCommit(commit);
});
doInTransaction(session -> {Repository repository = (Repository) session.get(Repository.class, 1L);assertEquals(3, repository.getCommits().size());
});

运行此测试将生成以下输出:

--Adding invalidates Collection Cacheinsert
intocommit(id, repository_id, review)
values(default, 1, false)insert
intocommit_change(commit_id, index_id, diff, path)
values(3, 0, '0b3,17...', 'Main.java')--committed JDBC Connectionselectcommits0_.repository_id as reposito3_0_0_,commits0_.id as id1_1_0_,commits0_.id as id11_1_1_,commits0_.repository_id as reposito3_1_1_,commits0_.review as review2_1_1_
fromcommit commits0_
wherecommits0_.repository_id=1--committed JDBC Connection

保留新的Commit实体后,将清除Repository.commits集合缓存,并从数据库中获取关联的Commits实体(下次访问该集合)。

删除现有的集合条目

删除Collection元素遵循相同的模式:

LOGGER.info("Removing invalidates Collection Cache");
doInTransaction(session -> {Repository repository = (Repository) session.get(Repository.class, 1L);assertEquals(2, repository.getCommits().size());Commit removable = repository.getCommits().get(0);repository.removeCommit(removable);
});
doInTransaction(session -> {Repository repository = (Repository) session.get(Repository.class, 1L);assertEquals(1, repository.getCommits().size());
});

生成以下输出:

--Removing invalidates Collection Cachedelete
fromcommit_change
wherecommit_id=1delete
fromcommit
whereid=1--committed JDBC Connectionselectcommits0_.repository_id as reposito3_0_0_,commits0_.id as id1_1_0_,commits0_.id as id1_1_1_,commits0_.repository_id as reposito3_1_1_,commits0_.review as review2_1_1_
fromcommit commits0_
wherecommits0_.repository_id=1--committed JDBC Connection

一旦更改其结构,便会收回集合缓存。

直接删除集合元素

只要Hibernate知道目标缓存集合要进行的所有更改,它就可以确保缓存的一致性。 Hibernate使用其自己的Collection类型(例如PersistentBag , PersistentSet )来允许延迟加载或检测脏状态 。

如果删除内部Collection元素而不更新Collection状态,则Hibernate将无法使当前缓存的Collection条目无效:

LOGGER.info("Removing Child causes inconsistencies");
doInTransaction(session -> {Commit commit = (Commit) session.get(Commit.class, 1L);session.delete(commit);
});
try {doInTransaction(session -> {Repository repository = (Repository) session.get(Repository.class, 1L);assertEquals(1, repository.getCommits().size());});
} catch (ObjectNotFoundException e) {LOGGER.warn("Object not found", e);
}
--Removing Child causes inconsistenciesdelete
fromcommit_change
wherecommit_id=1delete
fromcommit
whereid=1-committed JDBC Connectionselectcollection0_.id as id1_1_0_,collection0_.repository_id as reposito3_1_0_,collection0_.review as review2_1_0_
fromcommit collection0_
wherecollection0_.id=1--No row with the given identifier exists:
-- [CollectionCacheTest$Commit#1]--rolled JDBC Connection

Commit实体被删除时,Hibernate不知道它必须更新所有关联的Collection Cache。 下次加载Commit集合时,Hibernate将意识到某些实体不再存在,并且将引发异常。

使用HQL更新Collection元素

通过HQL执行批量更新时,Hibernate可以保持缓存一致性:

LOGGER.info("Updating Child entities using HQL");
doInTransaction(session -> {Repository repository = (Repository)session.get(Repository.class, 1L);for (Commit commit : repository.getCommits()) {assertFalse(commit.review);}
});
doInTransaction(session -> {session.createQuery("update Commit c " +"set c.review = true ").executeUpdate();
});
doInTransaction(session -> {Repository repository = (Repository)session.get(Repository.class, 1L);for(Commit commit : repository.getCommits()) {assertTrue(commit.review);}
});

运行此测试用例将生成以下SQL:

--Updating Child entities using HQL--committed JDBC Connectionupdatecommit
setreview=true--committed JDBC Connectionselectcommits0_.repository_id as reposito3_0_0_,commits0_.id as id1_1_0_,commits0_.id as id1_1_1_,commits0_.repository_id as reposito3_1_1_,commits0_.review as review2_1_1_
fromcommit commits0_
wherecommits0_.repository_id=1--committed JDBC Connection

第一个事务不需要命中数据库,仅依赖于第二级缓存。 HQL UPDATE清除了集合缓存,因此,在随后访问集合时,Hibernate将不得不从数据库中重新加载它。

使用SQL更新Collection元素

Hibernate还可以使批量SQL UPDATE语句的缓存条目无效:

LOGGER.info("Updating Child entities using SQL");
doInTransaction(session -> {Repository repository = (Repository) session.get(Repository.class, 1L);for (Commit commit : repository.getCommits()) {assertFalse(commit.review);}
});
doInTransaction(session -> {session.createSQLQuery("update Commit c " +"set c.review = true ").addSynchronizedEntityClass(Commit.class).executeUpdate();
});
doInTransaction(session -> {Repository repository = (Repository) session.get(Repository.class, 1L);for(Commit commit : repository.getCommits()) {assertTrue(commit.review);}
});

生成以下输出:

--Updating Child entities using SQL--committed JDBC Connectionupdatecommit
setreview=true--committed JDBC Connectionselectcommits0_.repository_id as reposito3_0_0_,commits0_.id as id1_1_0_,commits0_.id as id1_1_1_,commits0_.repository_id as reposito3_1_1_,commits0_.review as review2_1_1_
fromcommit commits0_
wherecommits0_.repository_id=1  --committed JDBC Connection

BulkOperationCleanupAction负责清理大容量DML语句上的二级缓存。 尽管Hibernate在执行HQL语句时可以检测到受影响的缓存区域,但是对于本机查询,您需要指示Hibernate该语句应使哪些区域无效。 如果您未指定任何此类区域,则Hibernate将清除所有第二级缓存区域。

结论

集合缓存是一项非常有用的功能,是对第二级实体缓存的补充。 这样,我们可以存储整个实体图,从而减少了只读应用程序中的数据库查询工作量。 像使用AUTO刷新一样 ,Hibernate在执行本机查询时无法自省受影响的表空间。 为了避免一致性问题(使用AUTO刷新时)或缓存未命中(二级缓存),每当我们需要运行本机查询时,我们都必须显式声明目标表,因此Hibernate可以采取适当的措施(例如刷新或使缓存无效)地区)。

  • 代码可在GitHub上获得 。

翻译自: https://www.javacodegeeks.com/2015/05/how-does-hibernate-collection-cache-work.html

Hibernate Collection Cache如何工作相关推荐

  1. Hibernate READ_ONLY CacheConcurrencyStrategy如何工作

    介绍 正如我前面所解释的 ,企业的高速缓存需要勤奋. 由于数据在数据库( 记录系统 )和缓存层之间重复,因此我们需要确保两个单独的数据源不会分开. 如果缓存的数据是不可变的(数据库和缓存都无法修改它) ...

  2. Hibernate READ_WRITE CacheConcurrencyStrategy如何工作

    介绍 在我以前的文章中,我介绍了NONSTRICT_READ_WRITE二级缓存并发机制. 在本文中,我将使用READ_WRITE策略继续本主题. 直写式缓存 NONSTRICT_READ_WRITE ...

  3. Hibernate查询缓存如何工作

    介绍 既然我已经介绍了实体和集合缓存,现在该研究查询缓存的工作原理了. 查询缓存与实体严格相关,它在搜索条件和满足该特定查询过滤器的实体之间绘制关联. 像其他Hibernate功能一样,查询缓存也不像 ...

  4. Struts1、Struts2、Hibernate、Spring框架工作原理介绍

    Struts1工作原理 Struts1工作原理图 1 .初始化: struts 框架的总控制器 ActionServlet 是一个 Servlet ,它在 web.xml 中配置成自动启动的 Serv ...

  5. SAP UI5 Negative cache的工作原理

    I am testing my Fiori extension project created based on SAP standard Fiori application "My Opp ...

  6. Hibernate Collection乐观锁定

    介绍 Hibernate提供了一种乐观的锁定机制 ,即使长时间通话也可以防止丢失更新 . 结合实体存储,跨越多个用户请求(扩展的持久性上下文或分离的实体),Hibernate可以保证应用程序级的可重复 ...

  7. 深入理解 Cache 工作原理

    欢迎关注方志朋的博客,回复"666"获面试宝典 大家好,今天给大家分享一篇关于 Cache 的硬核的技术文,基本上关于Cache的所有知识点都可以在这篇文章里看到. 关于 Cach ...

  8. Cache 工作原理,Cache 一致性,你想知道的都在这里

    欢迎关注方志朋的博客,回复"666"获面试宝典 可以随便到网上查一查,各大互联网公司笔试面试特别喜欢考一道算法题,即 LRU缓存机制,又顺手查了一下LRU缓存机制最近有哪些企业喜欢 ...

  9. Cache 工作原理、Cache 一致性,你想知道的都在这里

    作者 | 桔里猫 来源 | https://zhuanlan.zhihu.com/p/386919471 可以随便到网上查一查,各大互联网公司笔试面试特别喜欢考一道算法题,即 LRU缓存机制,又顺手查 ...

最新文章

  1. 两分公支的IPSec***流量走总部测试
  2. B站学强化学习?港中文周博磊变身up主,中文课程已上线
  3. 计算机管理信息系统大作业,管理信息系统期末大作业
  4. mysql 简单优化规则
  5. python的前端框架_web前端三大主流框架之Python异步框架如何工作?
  6. PHP多种序列化/反序列化的方法 (转载)
  7. [蓝桥杯2016初赛]方格填数
  8. python实现lenet_吴裕雄 python 神经网络TensorFlow实现LeNet模型处理手写数字识别MNIST数据集...
  9. 【汇编语言】(王爽)实验4解答
  10. 利用winrar自动备份重要资料
  11. MediaElementAudioSourceNode
  12. 20191001:String,StringBuffer,StringBuilder类异同辨析
  13. 2D转换中心点transform-origin(CSS3)
  14. 头衔的权威暗示影响力
  15. FPGA自动白平衡实现步骤详解
  16. linux 64 输入法下载,搜狗输入法 for Linux
  17. 机器学习--KNN算法应用,iris鸢尾花数据集的分类
  18. 用C语言做一个简单的原神抽卡小游戏
  19. 金蝶K3 SQL报表系列-委外未勾稽明细表
  20. 【342期】SpringBoot + Redis 布隆过滤器防恶意流量击穿缓存的正确姿势!

热门文章

  1. 干货!sqlserver数据库所有知识点总结整理,含代码(挺全的)
  2. ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
  3. js引擎执行代码的基本流程
  4. 2020蓝桥杯省赛---java---A---10( 字串排序)
  5. 什么叫做在oracle目录下,ORACLE directory 目录
  6. 如何写登录的记住账号
  7. 空调吸气和排气_吸气剂和二传手被认为有害
  8. php cdi_CDI中的事务异常处理
  9. java单例枚举_Java增强枚举的用例
  10. 怎么运行aws的示例程序_使Spring Boot应用程序在AWS上无服务器运行