Hibernate Collection Cache如何工作
介绍
之前,我描述了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如何工作相关推荐
- Hibernate READ_ONLY CacheConcurrencyStrategy如何工作
介绍 正如我前面所解释的 ,企业的高速缓存需要勤奋. 由于数据在数据库( 记录系统 )和缓存层之间重复,因此我们需要确保两个单独的数据源不会分开. 如果缓存的数据是不可变的(数据库和缓存都无法修改它) ...
- Hibernate READ_WRITE CacheConcurrencyStrategy如何工作
介绍 在我以前的文章中,我介绍了NONSTRICT_READ_WRITE二级缓存并发机制. 在本文中,我将使用READ_WRITE策略继续本主题. 直写式缓存 NONSTRICT_READ_WRITE ...
- Hibernate查询缓存如何工作
介绍 既然我已经介绍了实体和集合缓存,现在该研究查询缓存的工作原理了. 查询缓存与实体严格相关,它在搜索条件和满足该特定查询过滤器的实体之间绘制关联. 像其他Hibernate功能一样,查询缓存也不像 ...
- Struts1、Struts2、Hibernate、Spring框架工作原理介绍
Struts1工作原理 Struts1工作原理图 1 .初始化: struts 框架的总控制器 ActionServlet 是一个 Servlet ,它在 web.xml 中配置成自动启动的 Serv ...
- SAP UI5 Negative cache的工作原理
I am testing my Fiori extension project created based on SAP standard Fiori application "My Opp ...
- Hibernate Collection乐观锁定
介绍 Hibernate提供了一种乐观的锁定机制 ,即使长时间通话也可以防止丢失更新 . 结合实体存储,跨越多个用户请求(扩展的持久性上下文或分离的实体),Hibernate可以保证应用程序级的可重复 ...
- 深入理解 Cache 工作原理
欢迎关注方志朋的博客,回复"666"获面试宝典 大家好,今天给大家分享一篇关于 Cache 的硬核的技术文,基本上关于Cache的所有知识点都可以在这篇文章里看到. 关于 Cach ...
- Cache 工作原理,Cache 一致性,你想知道的都在这里
欢迎关注方志朋的博客,回复"666"获面试宝典 可以随便到网上查一查,各大互联网公司笔试面试特别喜欢考一道算法题,即 LRU缓存机制,又顺手查了一下LRU缓存机制最近有哪些企业喜欢 ...
- Cache 工作原理、Cache 一致性,你想知道的都在这里
作者 | 桔里猫 来源 | https://zhuanlan.zhihu.com/p/386919471 可以随便到网上查一查,各大互联网公司笔试面试特别喜欢考一道算法题,即 LRU缓存机制,又顺手查 ...
最新文章
- 两分公支的IPSec***流量走总部测试
- B站学强化学习?港中文周博磊变身up主,中文课程已上线
- 计算机管理信息系统大作业,管理信息系统期末大作业
- mysql 简单优化规则
- python的前端框架_web前端三大主流框架之Python异步框架如何工作?
- PHP多种序列化/反序列化的方法 (转载)
- [蓝桥杯2016初赛]方格填数
- python实现lenet_吴裕雄 python 神经网络TensorFlow实现LeNet模型处理手写数字识别MNIST数据集...
- 【汇编语言】(王爽)实验4解答
- 利用winrar自动备份重要资料
- MediaElementAudioSourceNode
- 20191001:String,StringBuffer,StringBuilder类异同辨析
- 2D转换中心点transform-origin(CSS3)
- 头衔的权威暗示影响力
- FPGA自动白平衡实现步骤详解
- linux 64 输入法下载,搜狗输入法 for Linux
- 机器学习--KNN算法应用,iris鸢尾花数据集的分类
- 用C语言做一个简单的原神抽卡小游戏
- 金蝶K3 SQL报表系列-委外未勾稽明细表
- 【342期】SpringBoot + Redis 布隆过滤器防恶意流量击穿缓存的正确姿势!
热门文章
- 干货!sqlserver数据库所有知识点总结整理,含代码(挺全的)
- ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
- js引擎执行代码的基本流程
- 2020蓝桥杯省赛---java---A---10( 字串排序)
- 什么叫做在oracle目录下,ORACLE directory 目录
- 如何写登录的记住账号
- 空调吸气和排气_吸气剂和二传手被认为有害
- php cdi_CDI中的事务异常处理
- java单例枚举_Java增强枚举的用例
- 怎么运行aws的示例程序_使Spring Boot应用程序在AWS上无服务器运行