Hibernate READ_ONLY CacheConcurrencyStrategy如何工作

介绍

正如我前面所解释的 ,企业的高速缓存需要勤奋。 由于数据在数据库( 记录系统 )和缓存层之间重复,因此我们需要确保两个单独的数据源不会分开。

如果缓存的数据是不可变的(数据库和缓存都无法修改它),我们可以安全地对其进行缓存,而不必担心任何一致性问题。 只读数据始终是应用程序级缓存的理想选择,可以在不放松一致性保证的情况下提高读取性能。

只读二级缓存

为了测试只读二级缓存策略,我们将使用以下域模型:

存储库提交更改只读缓存并发策略

存储库是根实体,是任何Commit实体的父代。 每个提交都有一个“ 更改”组件(可嵌入的值类型)列表。

所有实体都缓存为只读元素:

@org.hibernate.annotations.Cache(
    usage = CacheConcurrencyStrategy.READ_ONLY
)

持久实体

只读二级缓存使用一种直读缓存策略,在获取时会缓存实体。

doInTransaction(session -> {
    Repository repository = 
        new Repository("Hibernate-Master-Class");
    session.persist(repository);
});

当一个实体持久化时,只有数据库包含该实体的副本。 首次获取实体时,记录系统将传递到缓存层。

@Test
public void testRepositoryEntityLoad() {
    LOGGER.info("Read-only entities are read-through");

    doInTransaction(session -> {
        Repository repository = (Repository) 
            session.get(Repository.class, 1L);
        assertNotNull(repository);
    });

    doInTransaction(session -> {
        LOGGER.info("Load Repository from cache");
        session.get(Repository.class, 1L);
    });
}

此测试生成输出:

--Read-only entities are read-through

SELECT readonlyca0_.id   AS id1_2_0_,
       readonlyca0_.NAME AS name2_2_0_
FROM   repository readonlyca0_
WHERE  readonlyca0_.id = 1 

--JdbcTransaction - committed JDBC Connection

--Load Repository from cache

--JdbcTransaction - committed JDBC Connection

将实体加载到二级缓存后,缓存将为随后的所有调用提供服务,从而绕过数据库。

更新实体

只读缓存条目不允许更新。 任何此类尝试最终都会引发异常:

@Test
public void testReadOnlyEntityUpdate() {
    try {
        LOGGER.info("Read-only cache entries cannot be updated");
        doInTransaction(session -> {
            Repository repository = (Repository) 
                session.get(Repository.class, 1L);
            repository.setName(
                "High-Performance Hibernate"
            );
        });
    } catch (Exception e) {
        LOGGER.error("Expected", e);
    }
}

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

--Read-only cache entries cannot be updated

SELECT readonlyca0_.id   AS id1_2_0_,
       readonlyca0_.NAME AS name2_2_0_
FROM   repository readonlyca0_
WHERE  readonlyca0_.id = 1 

UPDATE repository
SET    NAME = 'High-Performance Hibernate'
WHERE  id = 1 

--JdbcTransaction - rolled JDBC Connection

--ERROR Expected
--java.lang.UnsupportedOperationException: Can't write to a readonly object

因为只读缓存实体实际上是不可变的,所以最好将它们赋予 Hibernate特有的@Immutable批注。

删除实体

同时删除关联的实体时,也会删除只读缓存条目:

@Test
public void testReadOnlyEntityDelete() {
    LOGGER.info("Read-only cache entries can be deleted");
    doInTransaction(session -> {
        Repository repository = (Repository) 
            session.get(Repository.class, 1L);
        assertNotNull(repository);
        session.delete(repository);
    });
    doInTransaction(session -> {
        Repository repository = (Repository) 
            session.get(Repository.class, 1L);
        assertNull(repository);
    });
}

生成以下输出:

--Read-only cache entries can be deleted

SELECT readonlyca0_.id   AS id1_2_0_,
       readonlyca0_.NAME AS name2_2_0_
FROM   repository readonlyca0_
WHERE  readonlyca0_.id = 1;

DELETE FROM repository
WHERE  id = 1

--JdbcTransaction - committed JDBC Connection

SELECT readonlyca0_.id   AS id1_2_0_,
       readonlyca0_.NAME AS name2_2_0_
FROM   repository readonlyca0_
WHERE  readonlyca0_.id = 1; 

--JdbcTransaction - committed JDBC Connection

PersistenceContext使移除 实体状态转换入队,并且在刷新时 ,数据库和二级缓存都将删除关联的实体记录。

集合缓存

提交实体具有变更组件的集合。

@ElementCollection
@CollectionTable(
    name="commit_change",
    joinColumns=@JoinColumn(name="commit_id")
)
private List<Change> changes = new ArrayList<>();

尽管Commit实体作为只读元素进行缓存,但第二级缓存将忽略Change集合。

@Test
public void testCollectionCache() {
    LOGGER.info("Collections require separate caching");
    doInTransaction(session -> {
        Repository repository = (Repository) 
            session.get(Repository.class, 1L);
        Commit commit = new Commit(repository);
        commit.getChanges().add(
            new Change("README.txt", "0a1,5...")
        );
        commit.getChanges().add(
            new Change("web.xml", "17c17...")
        );
        session.persist(commit);
    });
    doInTransaction(session -> {
        LOGGER.info("Load Commit from database");
        Commit commit = (Commit) 
            session.get(Commit.class, 1L);
        assertEquals(2, commit.getChanges().size());
    });
    doInTransaction(session -> {
        LOGGER.info("Load Commit from cache");
        Commit commit = (Commit) 
            session.get(Commit.class, 1L);
        assertEquals(2, commit.getChanges().size());
    });
}

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

--Collections require separate caching

SELECT readonlyca0_.id   AS id1_2_0_,
       readonlyca0_.NAME AS name2_2_0_
FROM   repository readonlyca0_
WHERE  readonlyca0_.id = 1;


INSERT INTO commit
            (id, repository_id)
VALUES      (DEFAULT, 1);
			 
INSERT INTO commit_change
            (commit_id, diff, path)
VALUES      (1, '0a1,5...', 'README.txt');		 

INSERT INTO commit_change
            (commit_id, diff, path)
VALUES      (1, '17c17...', 'web.xml');
			 
--JdbcTransaction - committed JDBC Connection

--Load Commit from database

SELECT readonlyca0_.id   AS id1_2_0_,
       readonlyca0_.NAME AS name2_2_0_
FROM   repository readonlyca0_
WHERE  readonlyca0_.id = 1;


SELECT changes0_.commit_id AS commit_i1_0_0_,
       changes0_.diff      AS diff2_1_0_,
       changes0_.path      AS path3_1_0_
FROM   commit_change changes0_
WHERE  changes0_.commit_id = 1 

--JdbcTransaction - committed JDBC Connection

--Load Commit from cache

SELECT changes0_.commit_id AS commit_i1_0_0_,
       changes0_.diff      AS diff2_1_0_,
       changes0_.path      AS path3_1_0_
FROM   commit_change changes0_
WHERE  changes0_.commit_id = 1 

--JdbcTransaction - committed JDBC Connection

尽管Commit实体是从缓存中检索的,但Change集合始终是从数据库中获取的。 由于更改也是不可变的,因此我们也希望对其进行缓存,以节省不必要的数据库往返次数。

启用集合缓存支持

默认情况下,不缓存集合,并且要启用此行为,我们必须使用缓存并发策略为它们添加注释:

@ElementCollection
@CollectionTable(
    name="commit_change",
    joinColumns=@JoinColumn(name="commit_id")
)
@org.hibernate.annotations.Cache(
    usage = CacheConcurrencyStrategy.READ_ONLY
)
private List<Change> changes = new ArrayList<>();

重新运行先前的测试将产生以下输出:

--Collections require separate caching

SELECT readonlyca0_.id   AS id1_2_0_,
       readonlyca0_.NAME AS name2_2_0_
FROM   repository readonlyca0_
WHERE  readonlyca0_.id = 1;


INSERT INTO commit
            (id, repository_id)
VALUES      (DEFAULT, 1);
			 
INSERT INTO commit_change
            (commit_id, diff, path)
VALUES      (1, '0a1,5...', 'README.txt');		 

INSERT INTO commit_change
            (commit_id, diff, path)
VALUES      (1, '17c17...', 'web.xml');
			 
--JdbcTransaction - committed JDBC Connection

--Load Commit from database

SELECT readonlyca0_.id   AS id1_2_0_,
       readonlyca0_.NAME AS name2_2_0_
FROM   repository readonlyca0_
WHERE  readonlyca0_.id = 1;


SELECT changes0_.commit_id AS commit_i1_0_0_,
       changes0_.diff      AS diff2_1_0_,
       changes0_.path      AS path3_1_0_
FROM   commit_change changes0_
WHERE  changes0_.commit_id = 1 

--JdbcTransaction - committed JDBC Connection

--Load Commit from cache

--JdbcTransaction - committed JDBC Connection

一旦集合被缓存,我们就可以获取Commit实体及其所有Change,而无需访问数据库。

结论

只读实体可以安全地进行缓存,我们可以仅使用第二级缓存来加载整个不可变实体图。 由于高速缓存是直通的 ,因此从数据库中获取实体时将对其进行高速缓存。 只读缓存不是直写的,因为持久存储实体只会在新的数据库行中实现,而不会传播到缓存中。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值