介绍
既然我已经介绍了实体和集合缓存,现在该研究查询缓存的工作原理了。
查询缓存与实体严格相关,它在搜索条件和满足该特定查询过滤器的实体之间绘制关联。 像其他Hibernate功能一样,查询缓存也不像人们想象的那么琐碎。
实体模型
对于我们的测试用例,我们将使用以下域模型:
Post实体与Author 具有多对一关联,并且两个实体都存储在第二级缓存中。
启用查询缓存
默认情况下,查询缓存处于禁用状态,要激活它,我们需要提供以下Hibernate属性:
properties.put("hibernate.cache.use_query_cache",
Boolean.TRUE.toString());
为了使Hibernate缓存给定的查询结果,我们需要在创建Query时显式设置cachable查询属性 。
直读缓存
查询缓存是只读的 ,就像NONSTRICT_READ_WRITE并发策略一样 ,它只能使陈旧的条目无效。
在下一个示例中,我们将缓存以下查询:
private List<Post> getLatestPosts(Session session) {
return (List<Post>) session.createQuery(
"select p " +
"from Post p " +
"order by p.createdOn desc")
.setMaxResults(10)
.setCacheable(true)
.list();
}
首先,我们将使用以下测试案例来研究查询缓存的内部结构:
doInTransaction(session -> {
LOGGER.info(
"Evict regions and run query");
session.getSessionFactory()
.getCache().evictAllRegions();
assertEquals(1, getLatestPosts(session).size());
});
doInTransaction(session -> {
LOGGER.info(
"Check get entity is cached");
Post post = (Post) session.get(Post.class, 1L);
});
doInTransaction(session -> {
LOGGER.info(
"Check query result is cached");
assertEquals(1, getLatestPosts(session).size());
});
该测试生成以下输出:
QueryCacheTest - Evict regions and run query
StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache
EhcacheGeneralDataRegion - Element for key sql:
select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
is null
StandardQueryCache - Query results were not found in cache
select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc limit 10
StandardQueryCache - Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp=5872026465492992
EhcacheGeneralDataRegion -
key:
sql: select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
value: [5872026465492992, 1]
JdbcTransaction - committed JDBC Connection
------------------------------------------------------------
QueryCacheTest - Check get entity is cached
JdbcTransaction - committed JDBC Connection
------------------------------------------------------------
QueryCacheTest - Check query is cached
StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache
StandardQueryCache - Checking query spaces are up-to-date: [Post]
EhcacheGeneralDataRegion - key: Post
UpdateTimestampsCache - [Post] last update timestamp: 5872026465406976, result set timestamp: 5872026465492992
StandardQueryCache - Returning cached query results
JdbcTransaction - committed JDBC Connection
- 清除所有缓存区域,以确保缓存为空
- 运行后查询后,查询缓存将检查以前存储的结果
- 因为没有缓存条目,所以查询转到数据库
- 所选实体和查询结果均被缓存
- 然后,我们验证Post实体是否存储在二级缓存中
- 后续查询请求将从缓存中解决,而无需访问数据库
查询参数
查询参数嵌入在缓存条目键中,如以下示例所示。
基本类型
首先,我们将使用基本的类型过滤:
private List<Post> getLatestPostsByAuthorId(Session session) {
return (List<Post>) session.createQuery(
"select p " +
"from Post p " +
"join p.author a " +
"where a.id = :authorId " +
"order by p.createdOn desc")
.setParameter("authorId", 1L)
.setMaxResults(10)
.setCacheable(true)
.list();
}
doInTransaction(session -> {
LOGGER.info("Query cache with basic type parameter");
List<Post> posts = getLatestPostsByAuthorId(session);
assertEquals(1, posts.size());
});
查询缓存条目如下所示:
EhcacheGeneralDataRegion -
key:
sql:
select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
inner join
Author querycache1_
on querycache0_.author_id=querycache1_.id
where
querycache1_.id=?
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {authorId=1};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
value: [5871781092679680, 1]
该参数存储在高速缓存条目键中。 缓存条目值的第一个元素始终是结果集的获取时间戳。 以下元素是此查询返回的实体标识符。
实体类型
我们还可以使用实体类型作为查询参数:
private List<Post> getLatestPostsByAuthor(Session session) {
Author author = (Author) session.get(Author.class, 1L);
return (List<Post>) session.createQuery(
"select p " +
"from Post p " +
"join p.author a " +
"where a = :author " +
"order by p.createdOn desc")
.setParameter("author", author)
.setMaxResults(10)
.setCacheable(true)
.list();
}
doInTransaction(session -> {
LOGGER.info("Query cache with entity type parameter");
List<Post> posts = getLatestPostsByAuthor(session);
assertEquals(1, posts.size());
});
缓存条目与我们之前的示例相似,因为Hibernate仅将实体标识符存储在缓存条目键中。 这很有意义,因为Hibernate已经缓存了Author实体。
EhcacheGeneralDataRegion -
key:
sql: select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
inner join
Author querycache1_
on querycache0_.author_id=querycache1_.id
where
querycache1_.id=?
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {author=1};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
value: [5871781092777984, 1]
一致性
HQL / JPQL查询无效
Hibernate二级缓存偏向于强一致性,而查询缓存也不例外。 与刷新一样,只要关联的表空间发生更改,查询缓存就可以使其条目无效。 每次我们持久/删除/更新实体时 ,使用该特定表的所有查询缓存条目都将失效。
doInTransaction(session -> {
Author author = (Author)
session.get(Author.class, 1L);
assertEquals(1, getLatestPosts(session).size());
LOGGER.info("Insert a new Post");
Post newPost = new Post("Hibernate Book", author);
session.persist(newPost);
session.flush();
LOGGER.info("Query cache is invalidated");
assertEquals(2, getLatestPosts(session).size());
});
doInTransaction(session -> {
LOGGER.info("Check Query cache");
assertEquals(2, getLatestPosts(session).size());
});
该测试将添加一个新的Post ,然后重新运行可缓存的查询。 运行此测试将给出以下输出:
QueryCacheTest - Insert a new Post
insert
into
Post
(id, author_id, created_on, name)
values
(default, 1, '2015-06-06 17:29:59.909', 'Hibernate Book')
UpdateTimestampsCache - Pre-invalidating space [Post], timestamp: 5872029941395456
EhcacheGeneralDataRegion - key: Post value: 5872029941395456
QueryCacheTest - Query cache is invalidated
StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache
EhcacheGeneralDataRegion -
key:
sql: select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
StandardQueryCache - Checking query spaces are up-to-date: [Post]
EhcacheGeneralDataRegion - key: Post
UpdateTimestampsCache - [Post] last update timestamp: 5872029941395456, result set timestamp: 5872029695619072
StandardQueryCache - Cached query results were not up-to-date
select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc limit 10
StandardQueryCache - Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp=5872029695668224
EhcacheGeneralDataRegion -
key:
sql: select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
value: [5872029695668224, 2, 1]
JdbcTransaction - committed JDBC Connection
UpdateTimestampsCache - Invalidating space [Post], timestamp: 5872029695680512
EhcacheGeneralDataRegion - key: Post value: 5872029695680512
------------------------------------------------------------
QueryCacheTest - Check Query cache
StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache
EhcacheGeneralDataRegion -
key:
sql: select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
StandardQueryCache - Checking query spaces are up-to-date: [Post]
EhcacheGeneralDataRegion - key: Post
UpdateTimestampsCache - [Post] last update timestamp: 5872029695680512, result set timestamp: 5872029695668224
StandardQueryCache - Cached query results were not up-to-date
select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc limit 10
StandardQueryCache - Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp=5872029695705088
EhcacheGeneralDataRegion -
key:
sql: select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
value: [5872029695705088, 2, 1]
JdbcTransaction - committed JDBC Connection
- 一旦Hibernate检测到Entity状态转换 ,它将使受影响的查询缓存区域预先失效
- 不会删除查询缓存条目,但会更新其关联的时间戳
- 查询缓存始终检查条目键时间戳,如果键时间戳比结果集加载时间戳新,它将跳过读取其值。
- 如果当前会话重新运行此查询,则结果将再次被缓存
- 当前数据库事务的提交和更改从会话级隔离传播到常规读取一致性
- 发生实际的无效,并且缓存条目时间戳再次更新
这种方法可能会破坏READ COMMITTED一致性保证,因为可能进行脏读 ,因为在提交数据库事务之前,当前隔离的更改会传播到Cache。
本机查询无效
正如我前面提到 ,本机查询休眠留在黑暗中,因为它可以不知道本地查询最终可能会修改其表。 在以下测试中,我们将更新Author表,同时检查它对当前Post Query Cache的影响:
doInTransaction(session -> {
assertEquals(1, getLatestPosts(session).size());
LOGGER.info("Execute native query");
assertEquals(1, session.createSQLQuery(
"update Author set name = '\"'||name||'\"' "
).executeUpdate());
LOGGER.info("Check query cache is invalidated");
assertEquals(1, getLatestPosts(session).size());
});
测试生成以下输出:
QueryCacheTest - Execute native query
UpdateTimestampsCache - Pre-invalidating space [Author], timestamp: 5872035446091776
EhcacheGeneralDataRegion - key: Author value: 5872035446091776
UpdateTimestampsCache - Pre-invalidating space [Post], timestamp: 5872035446091776
EhcacheGeneralDataRegion - key: Post value: 5872035446091776
update
Author
set
name = '"'||name||'"'
QueryCacheTest - Check query cache is invalidated
StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache
EhcacheGeneralDataRegion -
key:
sql: select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
StandardQueryCache - Checking query spaces are up-to-date: [Post]
EhcacheGeneralDataRegion - key: Post
UpdateTimestampsCache - [Post] last update timestamp: 5872035446091776, result set timestamp: 5872035200290816
StandardQueryCache - Cached query results were not up-to-date
select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc limit 10
StandardQueryCache - Caching query results in region: org.hibernate.cache.internal.StandardQueryCache; timestamp=5872035200364544
EhcacheGeneralDataRegion -
key:
sql: select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
value: [5872035200364544, 1]
JdbcTransaction - committed JDBC Connection
UpdateTimestampsCache - Invalidating space [Post], timestamp: 5872035200372736
EhcacheGeneralDataRegion - key: Post value: 5872035200372736
UpdateTimestampsCache - Invalidating space [Author], timestamp: 5872035200372736
EhcacheGeneralDataRegion - key: Author value: 5872035200372736
即使仅修改了Author表, Author和Post缓存区域也都无效。 为了解决这个问题,我们需要让Hibernate知道我们要更改哪些表。
本机查询缓存区域同步
Hibernate允许我们通过查询同步提示来定义查询表空间。 提供此信息时,Hibernate可使请求的缓存区域无效:
doInTransaction(session -> {
assertEquals(1, getLatestPosts(session).size());
LOGGER.info("Execute native query with synchronization");
assertEquals(1, session.createSQLQuery(
"update Author set name = '\"'||name||'\"' "
).addSynchronizedEntityClass(Author.class)
.executeUpdate());
LOGGER.info("Check query cache is not invalidated");
assertEquals(1, getLatestPosts(session).size());
});
正在生成以下输出:
QueryCacheTest - Execute native query with synchronization
UpdateTimestampsCache - Pre-invalidating space [Author], timestamp: 5872036893995008
EhcacheGeneralDataRegion - key: Author value: 5872036893995008
update
Author
set
name = '"'||name||'"'
QueryCacheTest - Check query cache is not invalidated
StandardQueryCache - Checking cached query results in region: org.hibernate.cache.internal.StandardQueryCache
EhcacheGeneralDataRegion -
key:
sql: select
querycache0_.id as id1_1_,
querycache0_.author_id as author_i4_1_,
querycache0_.created_on as created_2_1_,
querycache0_.name as name3_1_
from
Post querycache0_
order by
querycache0_.created_on desc;
parameters: ;
named parameters: {};
max rows: 10;
transformer: org.hibernate.transform.CacheableResultTransformer@110f2
StandardQueryCache - Checking query spaces are up-to-date: [Post]
EhcacheGeneralDataRegion - key: Post
UpdateTimestampsCache - [Post] last update timestamp: 5872036648169472, result set timestamp: 5872036648226816
StandardQueryCache - Returning cached query results
JdbcTransaction - committed JDBC Connection
UpdateTimestampsCache - Invalidating space [Author], timestamp: 5872036648263680
EhcacheGeneralDataRegion - key: Author value: 5872036648263680
只有提供的表空间无效,离开了邮政查询缓存不变。 可以将本机查询和查询缓存混合在一起,但是需要一些努力。
结论
查询缓存可以提高频繁执行的实体查询的应用程序性能,但这不是免费的。 它容易受到一致性问题的影响,并且如果没有适当的内存管理控制机制,它很容易变得很大。
代码可在GitHub上获得 。
翻译自: https://www.javacodegeeks.com/2015/06/how-does-hibernate-query-cache-work.html