介绍

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

直写式缓存

NONSTRICT_READ_WRITE是一种通读缓存策略,可更新最终无效的缓存条目。 尽管这种策略可能很简单,但是随着写入操作的增加,性能会下降。 对于需要大量写入的应用程序,直写式高速缓存策略是更好的选择,因为高速缓存条目可以被日期化而不是被丢弃。

因为数据库是记录系统,并且数据库操作被包装在物理事务中,所以可以同步更新缓存(例如TRANSACTIONAL缓存并发策略的情况)或异步更新(在提交数据库事务之后)。

READ_WRITE策略是一种异步缓存并发机制,为了防止数据完整性问题(例如,陈旧的缓存条目),它使用了提供工作单元隔离保证的锁定机制。

插入资料

因为持久化的实体是唯一标识的(每个实体都分配给一个不同的数据库行),所以新创建的实体会在提交数据库事务后立即缓存:

@Override
public boolean afterInsert(Object key, Object value, Object version) throws CacheException {region().writeLock( key );try {final Lockable item = (Lockable) region().get( key );if ( item == null ) {region().put( key, new Item( value, version, region().nextTimestamp() ) );return true;}else {return false;}}finally {region().writeUnlock( key );}
}

对于要在插入时进行缓存的实体,它必须使用SEQUENCE生成器 ,该缓存由EntityInsertAction填充:

@Override
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) throws HibernateException {final EntityPersister persister = getPersister();if ( success && isCachePutEnabled( persister, getSession() ) ) {final CacheKey ck = getSession().generateCacheKey( getId(), persister.getIdentifierType(), persister.getRootEntityName() );final boolean put = cacheAfterInsert( persister, ck );}}postCommitInsert( success );
}

IDENTITY生成器不能与事务性的后写式第一级缓存设计配合使用,因此关联的EntityIdentityInsertAction不会缓存新插入的条目(至少在修复HHH-7964之前)。

从理论上讲,在数据库事务提交和第二级高速缓存插入之间,一个并发事务可能会加载新创建的实体,因此触发高速缓存插入。 虽然可能,但缓存同步滞后非常短,如果并发事务被交错,则只会使另一个事务命中数据库,而不是从缓存中加载实体。

更新资料

尽管插入实体是一个相当简单的操作,但是对于更新,我们需要同步数据库和缓存条目。 READ_WRITE并发策略采用锁定机制来确保数据完整性:

  1. Hibernate Transaction提交过程触发会话刷新
  2. EntityUpdateAction用Lock对象替换当前缓存条目
  3. update方法用于同步缓存更新,因此在使用异步缓存并发策略(如READ_WRITE)时不会执行任何操作
  4. 提交数据库事务 ,将调用after-transaction-completion回调
  5. EntityUpdateAction调用EntityRegionAccessStrategy的afterUpdate方法
  6. ReadWriteEhcacheEntityRegionAccessStrategy将Lock条目替换为实际的Item ,从而封装了实体分解状态

删除资料

从下面的序列图中可以看出,删除实体与更新过程类似:

  • Hibernate Transaction提交过程触发会话刷新
  • EntityDeleteAction用Lock对象替换当前的缓存条目
  • remove方法调用不执行任何操作,因为READ_WRITE是异步缓存并发策略
  • 提交数据库事务 ,将调用after-transaction-completion回调
  • EntityDeleteAction调用EntityRegionAccessStrategy的unlockItem方法
  • ReadWriteEhcacheEntityRegionAccessStrategy用另一个超时时间增加的Lock对象替换Lock条目

删除实体后,其关联的二级缓存条目将被一个Lock对象代替,该对象将发出任何随后的请求以从数据库读取而不是使用缓存条目。

锁定构造

ItemLock类都继承自Lockable类型,并且这两个类都有一个特定的策略,允许读取或写入缓存条目。

READ_WRITE 锁定对象

Lock类定义以下方法:

@Override
public boolean isReadable(long txTimestamp) {return false;
}@Override
public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {if ( txTimestamp > timeout ) {// if timedout then allow writereturn true;}if ( multiplicity > 0 ) {// if still locked then disallow writereturn false;}return version == null? txTimestamp > unlockTimestamp: versionComparator.compare( version, newVersion ) < 0;
}
  • Lock对象不允许读取缓存条目,因此任何后续请求都必须发送到数据库
  • 如果当前会话创建时间戳大于“锁定超时”阈值,则允许写入缓存条目
  • 如果至少一个会话设法锁定了该条目,则禁止进行任何写操作
  • 如果进入的实体状态已增加其版本,或者当前的会话创建时间戳大于当前的条目解锁时间戳,则可以使用Lock条目进行写操作

READ_WRITE 项目对象

Item类定义以下读取/写入访问策略:

@Override
public boolean isReadable(long txTimestamp) {return txTimestamp > timestamp;
}@Override
public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {return version != null && versionComparator.compare( version, newVersion ) < 0;
}
  • 仅在缓存条目创建时间之后启动的会话中才可读取项目
  • Item条目仅在传入实体状态已增加其版本时才允许写入

缓存条目并发控制

当保存和读取底层缓存条目时,将调用这些并发控制机制。

在调用ReadWriteEhcacheEntityRegionAccessStrategy get方法时读取缓存条目:

public final Object get(Object key, long txTimestamp) throws CacheException {readLockIfNeeded( key );try {final Lockable item = (Lockable) region().get( key );final boolean readable = item != null && item.isReadable( txTimestamp );if ( readable ) {return item.getValue();}else {return null;}}finally {readUnlockIfNeeded( key );}
}

缓存条目由ReadWriteEhcacheEntityRegionAccessStrategy putFromLoad方法编写:

public final boolean putFromLoad(Object key,Object value,long txTimestamp,Object version,boolean minimalPutOverride)throws CacheException {region().writeLock( key );try {final Lockable item = (Lockable) region().get( key );final boolean writeable = item == null || item.isWriteable( txTimestamp, version, versionComparator );if ( writeable ) {region().put( key, new Item( value, version, region().nextTimestamp() ) );return true;}else {return false;}}finally {region().writeUnlock( key );}
}

超时

如果数据库操作失败,则当前高速缓存条目将保留一个Lock对象,并且无法回滚到其先前的Item状态。 由于这个原因,锁必须超时,以允许将缓存条目替换为实际的Item对象。 EhcacheDataRegion定义以下超时属性:

private static final String CACHE_LOCK_TIMEOUT_PROPERTY = "net.sf.ehcache.hibernate.cache_lock_timeout";
private static final int DEFAULT_CACHE_LOCK_TIMEOUT = 60000;

除非我们重写net.sf.ehcache.hibernate.cache_lock_timeout属性,否则默认超时为60秒:

final String timeout = properties.getProperty(CACHE_LOCK_TIMEOUT_PROPERTY,Integer.toString( DEFAULT_CACHE_LOCK_TIMEOUT )
);

以下测试将模拟失败的数据库事务,因此我们可以观察到READ_WRITE缓存如何仅在超时阈值到期后才允许写入。 首先,我们将降低超时值,以减少缓存冻结时间:

properties.put("net.sf.ehcache.hibernate.cache_lock_timeout", String.valueOf(250));

我们将使用自定义拦截器手动回滚当前正在运行的事务:

@Override
protected Interceptor interceptor() {return new EmptyInterceptor() {@Overridepublic void beforeTransactionCompletion(Transaction tx) {if(applyInterceptor.get()) {tx.rollback();}}};
}

以下例程将测试锁定超时行为:

try {doInTransaction(session -> {Repository repository = (Repository)session.get(Repository.class, 1L);repository.setName("High-Performance Hibernate");applyInterceptor.set(true);});
} catch (Exception e) {LOGGER.info("Expected", e);
}
applyInterceptor.set(false);AtomicReference<Object> previousCacheEntryReference =new AtomicReference<>();
AtomicBoolean cacheEntryChanged = new AtomicBoolean();while (!cacheEntryChanged.get()) {doInTransaction(session -> {boolean entryChange;session.get(Repository.class, 1L);try {Object previousCacheEntry = previousCacheEntryReference.get();Object cacheEntry = getCacheEntry(Repository.class, 1L);entryChange = previousCacheEntry != null &&previousCacheEntry != cacheEntry;previousCacheEntryReference.set(cacheEntry);LOGGER.info("Cache entry {}", ToStringBuilder.reflectionToString(cacheEntry));if(!entryChange) {sleep(100);} else {cacheEntryChanged.set(true);}} catch (IllegalAccessException e) {LOGGER.error("Error accessing Cache", e);}});
}

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

selectreadwritec0_.id as id1_0_0_,readwritec0_.name as name2_0_0_,readwritec0_.version as version3_0_0_
fromrepository readwritec0_
wherereadwritec0_.id=1updaterepository
setname='High-Performance Hibernate',version=1
whereid=1 and version=0JdbcTransaction - rolled JDBC Connectionselectreadwritec0_.id as id1_0_0_,readwritec0_.name as name2_0_0_,readwritec0_.version as version3_0_0_
fromrepository readwritec0_
wherereadwritec0_.id = 1Cache entry net.sf.ehcache.Element@3f9a0805[key=ReadWriteCacheConcurrencyStrategyWithLockTimeoutTest$Repository#1,value=Lock Source-UUID:ac775350-3930-4042-84b8-362b64c47e4b Lock-ID:0,version=1,hitCount=3,timeToLive=120,timeToIdle=120,lastUpdateTime=1432280657865,cacheDefaultLifespan=true,id=0
]
Wait 100 ms!
JdbcTransaction - committed JDBC Connectionselectreadwritec0_.id as id1_0_0_,readwritec0_.name as name2_0_0_,readwritec0_.version as version3_0_0_
fromrepository readwritec0_
wherereadwritec0_.id = 1Cache entry net.sf.ehcache.Element@3f9a0805[key=ReadWriteCacheConcurrencyStrategyWithLockTimeoutTest$Repository#1,value=Lock Source-UUID:ac775350-3930-4042-84b8-362b64c47e4b Lock-ID:0,version=1,hitCount=3,timeToLive=120,timeToIdle=120,lastUpdateTime=1432280657865,cacheDefaultLifespan=true,id=0
]
Wait 100 ms!
JdbcTransaction - committed JDBC Connectionselectreadwritec0_.id as id1_0_0_,readwritec0_.name as name2_0_0_,readwritec0_.version as version3_0_0_
fromrepository readwritec0_
wherereadwritec0_.id = 1
Cache entry net.sf.ehcache.Element@305f031[key=ReadWriteCacheConcurrencyStrategyWithLockTimeoutTest$Repository#1,value=org.hibernate.cache.ehcache.internal.strategy.AbstractReadWriteEhcacheAccessStrategy$Item@592e843a,version=1,hitCount=1,timeToLive=120,timeToIdle=120,lastUpdateTime=1432280658322,cacheDefaultLifespan=true,id=0
]
JdbcTransaction - committed JDBC Connection
  • 第一个事务尝试更新实体,因此在提交事务之前,关联的第二级缓存条目已被锁定。
  • 第一个事务失败,它被回滚
  • 持有锁,因此接下来的两个连续事务将进入数据库,而不用当前已加载的数据库实体状态替换Lock条目
  • 在Lock超时期限到期后,第三笔交易最终可以用Item缓存条目替换Lock (保持实体分解为水合状态

结论

READ_WRITE并发策略具有直写式缓存机制的优点,但是您需要了解它的内部工作原理,才能确定它是否适合您当前的项目数据访问要求。

对于繁重的写争用方案,锁定结构将使其他并发事务进入数据库,因此您必须确定同步高速缓存并发策略是否更适合这种情况。

  • 代码可在GitHub上获得 。

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

Hibernate READ_WRITE CacheConcurrencyStrategy如何工作相关推荐

  1. Hibernate READ_ONLY CacheConcurrencyStrategy如何工作

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

  2. Hibernate Collection Cache如何工作

    介绍 之前,我描述了Hibernate用于存储实体的二级缓存条目结构. 除了实体,Hibernate还可以存储实体关联,本文将阐明集合缓存的内部工作原理. 领域模型 对于即将进行的测试,我们将使用以下 ...

  3. HibernateNONSTRICT_READ_WRITE CacheConcurrencyStrategy如何工作

    介绍 在我以前的文章中 ,我介绍了READ_ONLY CacheConcurrencyStrategy ,这是不可变实体图的显而易见的选择. 当高速缓存的数据可变时,我们需要使用读写高速缓存策略,本文 ...

  4. 休眠NONSTRICT_READ_WRITE CacheConcurrencyStrategy如何工作

    介绍 在我以前的文章中 ,我介绍了READ_ONLY CacheConcurrencyStrategy ,这是不可变实体图的显而易见的选择. 当缓存的数据可变时,我们需要使用读写缓存策略,本文将介绍N ...

  5. Hibernate查询缓存如何工作

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

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

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

  7. Hibernate面试问题与解答

    Hibernate面试问题与解答 Hibernate是Java应用程序中使用最广泛的ORM工具之一.它在企业应用程序中用于数据库操作.所以我决定写一篇关于的帖子 hibernate面试问题,在面试前刷 ...

  8. Hibernate EHCache - Hibernate二级缓存

    Hibernate EHCache - Hibernate二级缓存 欢迎使用Hibernate二级缓存示例教程.今天我们将研究Hibernate EHCache,它是最受欢迎的Hibernate二级缓 ...

  9. HibernateEHCache –Hibernate二级缓存

    Welcome to the Hibernate Second Level Cache Example Tutorial. Today we will look into Hibernate EHCa ...

最新文章

  1. 为DataGrid行添加事件
  2. Java远程发送表单信息,java – 从html表单读取POST数据发送到serversocket
  3. 阿里云边缘容器服务、申通 IoT 云边端架构入选 2021 云边协同发展阶段性领先成果
  4. less 函数_Python中的函数式编程教程,学会用一行代码搞定所有内容
  5. ASP.NET Core Middleware
  6. jQuery实现表格行上移下移和置顶
  7. Sass基础知识及语法
  8. 大量开发者会将访问token和API密钥硬编码至Android应用
  9. php sql中文乱码怎么解决,php显示mssql中文乱码怎么办
  10. 学Web前端开发需要哪些基础?零基础小白该怎么入行?
  11. 利用 GitHub Actions 在 GitHub 上进行加密挖矿?
  12. 常见音视频编码格式一览
  13. 源码专题之spring设计模式:策略模式、原型模式、模板模式
  14. WIN7 vc2008【fatal error C1083: 无法打开文件:“Windows.h”: No such file or directory】【cl.exe link.exe手动调用编译】
  15. IEEE模板如何在abstract和keywords之间加一个段落Note to Practitioners
  16. ssh secure shell:server responded algorithm negotiation failed
  17. java 实现pdf转换成图片
  18. 国民岳父的“屁民理论”
  19. 金山打字专业文章计算机,练打字试卷_推荐几篇适合学生练习打字的文章_淘题吧...
  20. HTML+CSS模仿百度首页(gird+flex布局)

热门文章

  1. React类里面能写的东西
  2. springboot获取多个请求参数_springboot获取URL请求参数的多种方式
  3. stream 提取某字段_java8从list集合中取出某一属性的值的集合案例
  4. 第12步 用户模块前端(客户)
  5. mybatis generator Unknown system variable 'query_cache_size' 的解决方法
  6. java泛型程序设计——Varargs 警告+不能实例化类型变量
  7. 一文理清Cookie、Session、Token
  8. fork/join和线程池_从fork-join /线程池调用的Singelton bean中的访问spring请求范围缓存...
  9. 子类重写父类变量_为什么在子类中不重写超类的实例变量
  10. brew卸载jenv_使用brew,cask和jenv在MacOSX上设置多个Java JRE / JDK