
hibernate
介绍
正如我前面所解释的,企业的高速缓存需要勤奋。 因为数据在数据库(记录系统)和缓存层之间重复,所以我们需要确保两个单独的数据源不会分开。
如果缓存的数据是不可变的(数据库和缓存都无法修改它),我们可以安全地对其进行缓存,而不必担心任何一致性问题。 只读数据始终是应用程序级缓存的良好候选者,可以提高读取性能,而不必放松一致性保证。
只读二级缓存
为了测试只读二级缓存策略,我们将使用以下域模型:
存储库是根实体,是任何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,而无需访问数据库。
结论
只读实体可以安全地进行缓存,我们可以仅使用第二级缓存来加载整个不可变实体图。 因为高速缓存是直通的,所以从数据库中获取实体时就会对其进行高速缓存。 只读缓存不是直写的,因为持久存储实体只会在新的数据库行中实现,而不会传播到缓存中。
- 代码可在GitHub上获得。
hibernate