Hibernate Collection Cache如何工作

介绍

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

领域模型

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

collectioncacherepositorycommitchange

存储库具有一组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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/358725.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux php 版本切换,linux更换PHP版本,多个PHP版本切换

各位兄弟姐妹&#xff0c;linux下怎么进行更换PHP版本&#xff0c;切换不同的PHP版本呢&#xff1f;比如说我现在的PHP版本是5.3 我想要换成5.5 之后我可以在这两个版本间切换&#xff1f;我现在的情况是centos6.5 php版本是5.3 想要升级到5.5回复内容&#xff1a;各位兄弟姐妹…

配置防火墙打开 80 端口

1.打开iptables vi /etc/sysconfig/iptables 2.增加一行 -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT 3.重启防火墙 service iptables restart转载于:https://www.cnblogs.com/lanhuan/p/4561293.html

JSFuck奇葩的js编码

以前对黑客很崇拜&#xff0c;黑客的世界无比精彩。最近为了炫耀&#xff0c;想起了这段特殊的代码。 [][([外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1GiImO3K-1631794288635)((![][])][![]]([外链图片转存失败,源站可能有防盗链机制,建议将图片…

Bash脚本教程之行操作

目录 简介 光标移动 清除屏幕 编辑操作 自动补全 操作历史 基本用法 history 命令

安卓学习 intent

其实学习了好几个星期了&#xff0c;是看老罗的视频&#xff0c;但进度太慢 今天 换了一本书 Intent 切换页面 啊啊啊啊 CompentName compnew CompentName(MainActivity.this,SecondActivity.class); Intent intent new Intent(); intent.compentName(comp); startActivity(i…

Bash脚本教程之目录堆栈

目录 cd - pushd,popd dirs 命令 为了方便用户在不同目录之间切换,Bash 提供了目录堆栈功能。 cd - Bash 可以记忆用户进入过的目录。默认情况下,只记忆前一次所在的目录,cd -命令可以返回前一次的目录。 # 当前目录是 /path/to/foo $ cd bar# 重新回到 /path/to/foo…

php 映射程序,windows磁盘映射技术分享

磁盘映射就是将本地某个文件夹或者局域网中的某个计算机的某个目录映射成本地驱动器号&#xff0c;就是说把本地的文件夹或者网络上其他机器的共享的文件夹映射成为自己机器上的一个磁盘&#xff0c;这样可以可以更方便的打开相应的文档&#xff0c;下面作者分享几个在windows下…

模拟服务器和客户端交互的python脚本

脚本&#xff1a; 模拟服务器和客户端交互&#xff1a; import argparse, socket from datetime import datetimeIP "127.0.0.1" CODING "utf8" MAX_BYTES 65535 # UDP最大长度def server(port): # port&#xff1a;端口号sock socket.socket(socke…

Jersey WebResource –标头不附加

昨天我在使用Jersey WebResource类进行HTTP调用时遇到了一个奇怪的问题&#xff1a;似乎我设置的标头在执行HTTP调用时被忽略了。 提前阅读。 我试图实现的目标是调用REST Api从我们的应用程序中获取一些数据。 为此&#xff0c;我使用了Jersey客户端 &#xff0c;显然&#x…

动态规划 dynamic programming

动态规划dynamic programming June,7, 2015 作者&#xff1a;swanGooseMan 出处&#xff1a;http://www.cnblogs.com/swanGooseMan/p/4556588.html 声明&#xff1a;本文采用以下协议进行授权&#xff1a; 自由转载-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0 &…

Bash脚本教程之脚本入门

目录 Shebang 行 执行权限和路径 env 命令 注释 脚本参数 shift 命令 getopts 命令 配置项参数终止符 --

php m pi 2,PHP学习(2)

PHP 的自定义常量自定义常量需要我们使用define()函数去定义&#xff0c;用法为define(name,value,case_insensitive)其中case_insensitive为可选参数&#xff0c;规定常量是否区分大小写&#xff0c;值为true(不敏感)或者false(默认&#xff0c;敏感)例子&#xff1a;define(&…

Bash脚本教程之read命令

目录 用法 参数 IFS 变量 用法 有时,脚本需要在执行过程中,由用户提供一部分数据,这时可以使用read命令。它将用户的输入存入一个变量,方便后面的代码使用。用户按下回车键,就表示输入结束。 read命令的格式如下。 read [-options] [variable...] 上面语法中,optio…

利用Vulnhub复现漏洞 - JBoss JMXInvokerServlet 反序列化漏洞

JBoss JMXInvokerServlet 反序列化漏洞 Vulnhub官方复现教程漏洞原理 复现过程启动环境端口设置浏览器设置BurpSuit设置 复现漏洞序列化数据生成发送POCEXP Vulnhub官方复现教程 https://vulhub.org/#/environments/jboss/JMXInvokerServlet-deserialization/ 漏洞原理 这…

linux mysql 安装启动失败,Linux服务器一键安装包的mysql启动失败

Linux服务器上用一键安装包配置的环境&#xff0c;启动mysql失败&#xff0c;提示如下错误信息&#xff1a;排查方法&#xff1a;1、查看服务器的磁盘空间是否正常&#xff0c;登录服务器执行命令df -h查看磁盘空间&#xff0c;如果服务器的系统盘或者数据盘空间满了&#xff0…

Ubuntu 安装mysql和简单操作

ubuntu上安装mysql非常简单只需要几条命令就可以完成。 1. sudo apt-get install mysql-server2. apt-get isntall mysql-client3. sudo apt-get install libmysqlclient-dev安装过程中会提示设置密码什么的&#xff0c;注意设置了不要忘了&#xff0c;安装完成之后可以使用如…

卖家工具箱源码_我的测试和代码分析工具箱

卖家工具箱源码上周&#xff0c;我们在LINEAS成立了一个“测试技能小组”&#xff0c;该小组用于交换有关测试的知识。 各种各样的问题反复出现的一个问题是&#xff1a;有哪些工具可以测试和分析代码&#xff1f; 因此&#xff0c;这是我对此的个人回答&#xff0c;按照我倾向…

Bash脚本教程之条件判断

目录 if 结构 test 命令 判断表达式 文件判断 字符串判断 整数判断 正则判断 test 判断的逻辑运算

matlab工序能力分析,《MATLAB编程与系统仿真》课程考核说明

《MATLAB编程与系统仿真》课程考核说明1、考核方式及考核时间综合性仿真及报告书(60%)实验成绩(30%)平时成绩(10%)&#xff0c;其中实验成绩包括实验和报告。《MATLAB编程与系统仿真》课程是一门实践性比较强的课程&#xff0c;采用传统的试卷考核方式无法体现学生对MATLAB的掌…

在BurpSuite中安装Jython环境

在BurpSuite中安装Jython环境 下载模块 下载地址 https://www.jython.org/download.html 下载 Jython Standalone版本的 打开burp 第一个框子是刚刚下载jar包 第二个时候python的模块文件地址 要到 lib\site-packages里面 成功 转载于&#xff1a;https://blog.csdn.net/w…