hibernate_Hibernate Collection Cache如何工作

hibernate

hibernate

介绍

之前,我描述了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());
    }
});

并且首次访问集合时将对其进行缓存:

select
    collection0_.id as id1_0_0_,
    collection0_.name as name2_0_0_ 
from
    Repository collection0_ 
where
    collection0_.id=1  
    
select
    commits0_.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_ 
from
    commit commits0_ 
where
    commits0_.r  
        
select
    changes0_.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_ 
from
    commit_change changes0_ 
where
    changes0_.commit_id=1  
            
select
    changes0_.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_ 
from
    commit_change changes0_ 
where
    changes0_.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 = null
    hashCode = 31
value = {org.hibernate.cache.ehcache.internal.strategy.AbstractReadWriteEhcacheAccessStrategy$Item@3982} 
    value = {org.hibernate.cache.spi.entry.CollectionCacheEntry@3986} "CollectionCacheEntry[1,2]"
    version = null
    timestamp = 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 = null
    hashCode = 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 = null
    timestamp = 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规范也记录了此行为:

对于缓存的收集数据,所有修改操作实际上只会使条目无效。

根据当前的并发策略,收回集合缓存条目:

添加新的收藏夹条目

以下测试案例向我们的存储库添加了一个新的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 Cache

insert 
into
   commit
   (id, repository_id, review) 
values
   (default, 1, false)
   
insert 
into
   commit_change
   (commit_id, index_id, diff, path) 
values
   (3, 0, '0b3,17...', 'Main.java')
 
--committed JDBC Connection

select
   commits0_.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_ 
from
   commit commits0_ 
where
   commits0_.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 Cache

delete 
from
   commit_change 
where
   commit_id=1
   
delete 
from
   commit 
where
   id=1
   
--committed JDBC Connection

select
   commits0_.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_ 
from
   commit commits0_ 
where
   commits0_.repository_id=1
   
--committed JDBC Connection

一旦更改其结构,便会收回集合缓存。

直接删除集合元素

只要Hibernate知道目标缓存集合要进行的所有更改,它就可以确保缓存的一致性。 Hibernate使用其自己的Collection类型(例如PersistentBagPersistentSet )来允许延迟加载或检测脏状态

如果删除内部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 inconsistencies

delete 
from
   commit_change 
where
   commit_id=1
   
delete 
from
   commit 
where
   id=1

-committed JDBC Connection

select
   collection0_.id as id1_1_0_,
   collection0_.repository_id as reposito3_1_0_,
   collection0_.review as review2_1_0_ 
from
   commit collection0_ 
where
   collection0_.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 Connection

update
   commit 
set
   review=true
   
--committed JDBC Connection

select
   commits0_.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_ 
from
   commit commits0_ 
where
   commits0_.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 Connection

update
   commit 
set
   review=true
   
--committed JDBC Connection
   
select
   commits0_.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_ 
from
   commit commits0_ 
where
   commits0_.repository_id=1  
   
--committed JDBC Connection

BulkOperationCleanupAction负责清理大容量DML语句上的二级缓存。 尽管Hibernate在执行HQL语句时可以检测到受影响的缓存区域,但是对于本机查询,您需要指示Hibernate该语句应使哪些区域无效。 如果您未指定任何此类区域,则Hibernate将清除所有第二级缓存区域。

结论

集合缓存是一项非常有用的功能,是对第二级实体缓存的补充。 这样,我们可以存储整个实体图,从而减少了只读应用程序中的数据库查询工作量。 与自动刷新一样,Hibernate在执行本机查询时无法自省受影响的表空间。 为了避免一致性问题(使用AUTO刷新时)或缓存未命中(二级缓存),每当我们需要运行本机查询时,我们都必须明确声明目标表,以便Hibernate可以采取适当的操作(例如,刷新或使缓存无效)地区)。

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

hibernate

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值