Hibernate查询缓存如何工作

介绍

既然我已经介绍了实体集合缓存,现在该研究查询缓存的工作原理了。

查询缓存与实体严格相关,它在搜索条件和满足该特定查询过滤器的实体之间绘制关联。 像其他Hibernate功能一样,查询缓存也不像人们想象的那么琐碎。

实体模型

对于我们的测试用例,我们将使用以下域模型:

postauthorquerycache

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表, AuthorPost缓存区域也都无效。 为了解决这个问题,我们需要让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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值