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,一经查实,立即删除!

相关文章

模拟服务器和客户端交互的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…

动态规划 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 &…

利用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;安装完成之后可以使用如…

在BurpSuite中安装Jython环境

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

matlab dtft的函数,DTFT的Matlab矩阵计算的理解

其实是早应该想到的&#xff0c;今天写程序的时候想到了。然后&#xff0c;跟同学说起来&#xff0c;说&#xff0c;原来国外的教材很多都是矩阵的形式来表示离散傅里叶变换的。但是国内的教材没有这么表达。一个是&#xff0c;自己看的东西还是少了&#xff0c;一个是&#xf…

xss测试工具(xsstrike基于python)

xsstrike很强 项目地址&#xff1a; https://github.com/s0md3v/XSStrike安装&#xff1a; git clone https://github.com/s0md3v/XSStrike.git使用文档&#xff1a; https://github.com/s0md3v/XSStrike/wiki/Usageusage: xsstrike.py [-h] [-u TARGET] [--data DATA] [-t …

变量和字符串

变量名就像我们现实社会的名字&#xff0c;把一个值赋值给一个名字时&#xff0c;Ta会存储在内存中&#xff0c;称之为变量&#xff08;variable)&#xff0c;在大多数语言中&#xff0c;都把这种动作称为&#xff08;给变量赋值&#xff09;或&#xff08;把值存储在变量中&am…

Windows安全配置加固

安全配置加固——账号口令 账号优化 目的是为了梳理系统中的账号以及口令&#xff0c;避免默认账号及弱口令的存在 查看账号方式 在Windows中查看账号的方式有以下几种&#xff0c;这里就来简述一下 第一种&#xff1a;开始->运行->compmgmt.msc&#xff08;进入计算…

存根类 测试代码 java_常规单元测试和存根–测​​试技术4

存根类 测试代码 java我的上一个博客是有关测试代码的方法以及讨论您做什么和不必进行测试的方法的一系列博客中的第三篇。 它基于我使用一种非常常见的模式从数据库检索地址的简单方案&#xff1a; …并且我提出了这样的想法&#xff0c;即任何不包含任何逻辑的类实际上都不需…

使用 Python 爬取网页数据

在需要过去一些网页上的信息的时候&#xff0c;使用 Python 写爬虫来爬取十分方便。 1. 使用 urllib.request 获取网页 urllib 是 Python 內建的 HTTP 库, 使用 urllib 可以只需要很简单的步骤就能高效采集数据; 配合 Beautiful 等 HTML 解析库, 可以编写出用于采集网络数据的…

多线程(初级篇)

相关概念进程是指一个内存中运行的应用程序&#xff0c;每个进程都有自己独立的一块内存空间&#xff0c;一个进程中可以启动多个线程。一个进程是一个独立的运行环境&#xff0c;它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了…

了解ADF生命周期中的ADF绑定

在本文中&#xff0c;我将重点介绍ADF绑定层&#xff0c;并探讨当最初从浏览器请求带有某些数据的ADF页面时&#xff0c;它如何工作。 Oracle ADF提供了自己的JSF生命周期扩展版。 实际上&#xff0c;ADF扩展了标准JSF生命周期实现类&#xff0c;并提供了ADF阶段侦听器&#x…

绕过 WAF:绕过一些 WAF 设备的 Burp 插件

我写了这个插件使用的技术博客文章在这里一会儿回来。如果存在特定标头&#xff0c;许多 WAF 设备可能会被诱骗相信请求来自自身&#xff0c;因此是可信的。绕过方法的基础知识可以在此处的 HP 博客文章中找到。 一段时间以来&#xff0c;我一直在 Burp 中实施匹配/替换规则&…

windows apache部署php,Windows下部署Apache+PHP+MySQL运行环境实战

首先是MySQL&#xff0c;(这边吐槽一下被Oracle收购的MySQL)一步一步往下&#xff0c;无需更多的配置然后安装Apache&#xff0c;也是一步一步往下安装PHP&#xff0c;(我偷懒我自豪, 在PHP下载页面找那个Installer的文件.......吐槽可能不是最新的版本 &#xff5e;)下载页面安…

粉丝提问:有没有人会做彩虹表

彩虹表就是一个庞大的、针对各种可能的字母组合预先计算好的哈希值的集合&#xff0c;不一定是针对MD5算法的&#xff0c;各种算法的都有&#xff0c;有了它可以快速的破解各类密码。越是复杂的密码&#xff0c;需要的彩虹表就越大&#xff0c;现在主流的彩虹表都是100G以上。 …

fofa自动化爬虫脚本更新+详解

fofa自动化爬虫脚本更新详解 起因 最近要用到fofa爬虫&#xff0c;为什么要用爬虫不用api&#xff0c;问就是穷&#xff0c;想起来之前写过一个相关的脚本&#xff1a;Fofa-python-脚本&#xff0c;是很久以前写的了&#xff0c;之前写的时候有点问题&#xff0c;昨天重新修…

【APICloud系列|16】苹果开发者账号如何更改双重认证的手机号

按照一般的更改流程&#xff1a; 现在苹果账号安全系统升级&#xff0c;一般需要同意协议或者和本公司密切相关的人员才能操作。我这种借苹果手机操作的人除外。 那我用win7电脑如何操作呢&#xff1f; 登录苹果开发者账号&#xff0c; 进入如下管理账号地址&#xff1a;htt…

JS文件信息收集工具-LinkFinder

0x00 前言 我们在渗透测试的之前&#xff0c;信息收集是必要的步骤&#xff0c;而JS文件中往往会暴露出很多的信息&#xff0c;比如一些注释的中铭感信息&#xff0c;内网ip地址泄露等等&#xff0c;还会有一些绝对路径或者相对路径的url&#xff0c;而这些url中很有可能就存在…