Hibernate如何存储二级缓存条目

介绍

使用数据库访问抽象层的好处是可以透明地实现缓存,而不会泄漏到业务逻辑代码中 。 Hibernate Persistence Context充当事务后写式高速缓存 ,将实体状态转换转换DML语句。

持久性上下文充当逻辑事务存储,并且每个Entity实例最多可以具有一个托管引用。 无论我们尝试加载相同的实体多少次, 休眠会话都将始终返回相同的对象引用。 通常将此行为描述为第一级缓存

Hibernate持久化上下文 本身并不是一个缓存解决方案,服务于不同的目的不是提升应用程序读取操作的性能。 因为休眠会话绑定到当前正在运行的逻辑事务,所以一旦事务结束,该会话将被销毁。

二级缓存

适当的缓存解决方案必须跨越多个Hibernate会话 ,这就是Hibernate还支持其他二级缓存的原因。 第二级缓存绑定到SessionFactory生命周期,因此仅在关闭SessionFactory时会销毁它(通常是在应用程序关闭时)。 第二级缓存主要基于实体,尽管它也支持可选的查询缓存解决方案。

默认情况下,二级缓存是禁用的,要激活它,我们必须设置以下Hibernate属性:

properties.put("hibernate.cache.use_second_level_cache", 
    Boolean.TRUE.toString());
properties.put("hibernate.cache.region.factory_class", 
    "org.hibernate.cache.ehcache.EhCacheRegionFactory");

一旦将hibernate.cache.use_second_level_cache属性设置为trueRegionFactory将定义第二级缓存实现提供程序,并且hibernate.cache.region.factory_class配置是必需的。

要启用实体级缓存,我们需要如下注释可缓存实体:

@Entity
@org.hibernate.annotations.Cache(usage = 
    CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

JPA还定义了@Cacheable批注,但它不支持在实体级别设置并发策略

实体加载流程

每当要加载实体时, 都会触发LoadEevent并由 DefaultLoadEventListener对其进行如下处理:

Object entity = loadFromSessionCache( event, 
    keyToLoad, options );
if ( entity == REMOVED_ENTITY_MARKER ) {
    LOG.debug("Load request found matching entity 
        in context, but it is scheduled for removal;
        returning null" );
    return null;
}
if ( entity == INCONSISTENT_RTN_CLASS_MARKER ) {
    LOG.debug("Load request found matching entity 
        in context, but the matched entity was of
        an inconsistent return type;
        returning null"
    );
    return null;
}
if ( entity != null ) {
    if ( traceEnabled ) {
        LOG.tracev("Resolved object in "
            + "session cache: {0}",
            MessageHelper.infoString( persister,
                event.getEntityId(),
                event.getSession().getFactory() )
        );
    }
    return entity;
}

entity = loadFromSecondLevelCache( event, 
    persister, options );
if ( entity != null ) {
    if ( traceEnabled ) {
        LOG.tracev("Resolved object in "
            + "second-level cache: {0}",
            MessageHelper.infoString( persister,
                event.getEntityId(),
                event.getSession().getFactory() )
        );
    }
}
else {
    if ( traceEnabled ) {
        LOG.tracev("Object not resolved in "
            + "any cache: {0}",
            MessageHelper.infoString( persister,
                event.getEntityId(),
                event.getSession().getFactory() )
        );
    }
    entity = loadFromDatasource( event, persister, 
        keyToLoad, options );
}

始终首先检查该会话,因为它可能已经包含一个受管实体实例。 在访问数据库之前,已对二级缓存进行了验证,因此其主要目的是减少数据库访问的次数。

二级缓存内部

每个实体都存储为CacheEntry ,并且实体水合状态用于创建缓存条目值。

补水

在Hibernate命名法中, 水化是将JDBC ResultSet转换为原始值数组时:

final Object[] values = persister.hydrate(
    rs, id, object, 
    rootPersister, cols, eagerPropertyFetch, session
);

水合状态作为EntityEntry对象保存在当前运行的持久性上下文中 ,该对象封装了加载时实体快照。 然后通过以下方式使用水合状态:

  • 默认的脏检查机制 ,该机制将当前实体数据与加载时快照进行比较
  • 第二级缓存,其缓存项是根据加载时实体快照构建的

反向操作称为脱水 ,它将实体状态复制到INSERTUPDATE语句中。

二级缓存元素

尽管Hibernate允许我们操纵实体图,但是二级缓存使用反汇编的水合状态代替:

final CacheEntry entry = persister.buildCacheEntry( 
    entity, hydratedState, version, session );

水合状态在存储在CacheEntry中之前先进行分解:

this.disassembledState = TypeHelper.disassemble(
    state, persister.getPropertyTypes(),
    persister.isLazyPropertiesCacheable() 
        ? null : persister.getPropertyLaziness(),
    session, owner
);

从以下实体模型图开始:

后注释详细信息第二级缓存

我们将插入以下实体:

Post post = new Post();
post.setName("Hibernate Master Class");

post.addDetails(new PostDetails());
post.addComment(new Comment("Good post!"));
post.addComment(new Comment("Nice post!"));

session.persist(post);

现在,我们将检查每个单独的实体缓存元素。

邮政实体有一个一对多关联的注释实体和逆一个-to-one关联到PostDetails:

@OneToMany(cascade = CascadeType.ALL, 
    mappedBy = "post")
private List<Comment> comments = new ArrayList<>();

@OneToOne(cascade = CascadeType.ALL, 
    mappedBy = "post", optional = true)
private PostDetails details;

提取Post实体时:

Post post = (Post) session.get(Post.class, 1L);

关联的缓存元素如下所示:

key = {org.hibernate.cache.spi.CacheKey@3855}
    key = {java.lang.Long@3860} "1"
    type = {org.hibernate.type.LongType@3861} 
    entityOrRoleName = {java.lang.String@3862} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$Post"
    tenantId = null
    hashCode = 31
value = {org.hibernate.cache.spi.entry.StandardCacheEntryImpl@3856}
    disassembledState = {java.io.Serializable[3]@3864} 
        0 = {java.lang.Long@3860} "1"
        1 = {java.lang.String@3865} "Hibernate Master Class"
    subclass = {java.lang.String@3862} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$Post"
    lazyPropertiesAreUnfetched = false
    version = null

CacheKey包含实体标识符,而CacheEntry包含实体分解的水合状态。

Post条目缓存值由name列和id组成 ,它们由一对多 Comment关联设置。

一对多关联或反向一对一关联都没有嵌入Post CacheEntry中

PostDetails实体主键是引用相关帖子实体的主键 ,因此具有与邮政实体一到一对一的关联。

@OneToOne
@JoinColumn(name = "id")
@MapsId
private Post post;

提取PostDetails实体时:

PostDetails postDetails = 
    (PostDetails) session.get(PostDetails.class, 1L);

第二级缓存生成以下缓存元素:

key = {org.hibernate.cache.spi.CacheKey@3927}
    key = {java.lang.Long@3897} "1"
    type = {org.hibernate.type.LongType@3898} 
    entityOrRoleName = {java.lang.String@3932} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$PostDetails"
    tenantId = null
    hashCode = 31
value = {org.hibernate.cache.spi.entry.StandardCacheEntryImpl@3928}
    disassembledState = {java.io.Serializable[2]@3933} 
        0 = {java.sql.Timestamp@3935} "2015-04-06 15:36:13.626"
    subclass = {java.lang.String@3932} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$PostDetails"
    lazyPropertiesAreUnfetched = false
    version = null

由于实体标识符嵌入在CacheKey中 ,因此反汇编状态仅包含createdOn实体属性。

Comment实体与Post 具有多对一关联:

@ManyToOne
private Post post;

当我们获取评论实体时:

Comment comments = 
    (Comment) session.get(Comment.class, 1L);

Hibernate生成以下二级缓存元素:

key = {org.hibernate.cache.spi.CacheKey@3857}
    key = {java.lang.Long@3864} "2"
    type = {org.hibernate.type.LongType@3865} 
    entityOrRoleName = {java.lang.String@3863} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$Comment"
    tenantId = null
    hashCode = 62
value = {org.hibernate.cache.spi.entry.StandardCacheEntryImpl@3858}
    disassembledState = {java.io.Serializable[2]@3862} 
        0 = {java.lang.Long@3867} "1"
        1 = {java.lang.String@3868} "Good post!"
    subclass = {java.lang.String@3863} "com.vladmihalcea.hibernate.masterclass.laboratory.cache.SecondLevelCacheTest$Comment"
    lazyPropertiesAreUnfetched = false
    version = null

反汇编状态包含Post.id 外键引用和检查列,因此镜像了关联的数据库表定义。

结论

第二级缓存是关系数据缓存,因此它以规范化形式存储数据,并且每个实体更新仅影响一个缓存条目。 无法读取整个实体图,因为在第二级缓存条目中未实现实体关联。

聚合实体图以使写入操作复杂化为代价,为读取操作提供了更好的性能。 如果缓存的数据未规范化并散布在各种聚合模型中,则实体更新将不得不修改多个缓存项,从而影响写入操作性能。

由于它反映了基础关系数据,因此二级缓存提供了各种并发策略机制,因此我们可以平衡读取性能和强大的一致性保证。

翻译自: https://www.javacodegeeks.com/2015/04/how-does-hibernate-store-second-level-cache-entries.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值