Hibernate Second-Level Cache – 冬眠的二级缓存

最后修改: 2016年 8月 16日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

One of the advantages of database abstraction layers, such as ORM (object-relational mapping) frameworks, is their ability to transparently cache data retrieved from the underlying store. This helps eliminate database-access costs for frequently accessed data.

数据库抽象层的优势之一,如ORM(对象关系映射)框架,是其透明地缓存数据的能力从底层存储中检索。这有助于消除频繁访问数据的数据库访问成本。

Performance gains can be significant if the read/write ratios of cached content are high. This is especially true for entities which consist of large object graphs.

如果缓存内容的读/写比率很高,那么性能的提高就会很明显。这对于由大型对象图组成的实体来说尤其如此。

In this tutorial, we’ll explore Hibernate second-level cache. We’ll explain some basic concepts, and illustrate everything with simple examples. We’ll use JPA, and fall back to Hibernate native API only for those features that aren’t standardized in JPA.

在本教程中,我们将探讨Hibernate二级缓存。我们将解释一些基本概念,并通过简单的例子来说明一切。我们将使用JPA,只有在JPA中没有标准化的功能时才会回到Hibernate本地API。

2. What Is a Second-Level Cache?

2.什么是二级缓存?

As with most other fully-equipped ORM frameworks, Hibernate has the concept of a first-level cache. It’s a session scoped cache which ensures that each entity instance is loaded only once in the persistent context.

和其他大多数装备齐全的ORM框架一样,Hibernate有一个一级缓存的概念。这是一个会话范围内的缓存,确保每个实体实例在持久化上下文中只被加载一次。

Once the session is closed, the first-level cache is terminated as well. This is actually desirable, as it allows for concurrent sessions to work with entity instances in isolation from each other.

一旦会话被关闭,一级缓存也被终止。这实际上是可取的,因为它允许并发的会话在相互隔离的情况下处理实体实例。

Conversely, a second-level cache is SessionFactory-scoped, meaning it’s shared by all sessions created with the same session factory. When an entity instance is looked up by its id (either by application logic or by Hibernate internally, e.g. when it loads associations to that entity from other entities), and second-level caching is enabled for that entity, the following happens:

相反,二级缓存是SessionFactory范围的,这意味着它被所有使用同一会话工厂创建的会话所共享。当一个实体实例被其id查找时(无论是由应用逻辑还是由Hibernate内部查找,例如当它从其他实体加载到该实体的关联时),并且为该实体启用了二级缓存,会发生以下情况。

  • If an instance is already present in the first-level cache, it’s returned from there.
  • If an instance isn’t found in the first-level cache, and the corresponding instance state is cached in the second-level cache, then the data is fetched from there and an instance is assembled and returned.
  • Otherwise, the necessary data are loaded from the database and an instance is assembled and returned.

Once the instance is stored in the persistence context (first-level cache), it’s returned from there in all subsequent calls within the same session until the session is closed, or the instance is manually evicted from the persistence context. The loaded instance state is also stored in the L2 cache if it wasn’t already there.

一旦实例被存储在持久化上下文(一级缓存)中,它就会在同一会话中的所有后续调用中被返回,直到会话被关闭,或者实例被手动从持久化上下文中驱逐。加载的实例状态也被存储在二级缓存中,如果它还没有被存储在那里的话。

3. Region Factory

3.地区工厂

Hibernate second-level caching is designed to be unaware of the actual cache provider used. Hibernate only needs to be provided with an implementation of the org.hibernate.cache.spi.RegionFactory interface, which encapsulates all the details specific to the actual cache providers. Basically, it acts as a bridge between Hibernate and cache providers.

Hibernate二级缓存被设计为不知道实际使用的缓存提供者。Hibernate只需要提供一个org.hibernate.cache.spi.RegionFactory接口的实现,该接口封装了所有特定于实际缓存提供者的细节。基本上,它充当了Hibernate和缓存提供者之间的桥梁。

In this article, we’ll use Ehcache, a mature and widely used cache, as our cache provider. We could pick any other provider instead, as long as there’s an implementation of a RegionFactory for it.

在本文中,我们将使用Ehcache,一个成熟且广泛使用的缓存,作为我们的缓存提供者。我们可以选择任何其他提供者,只要有一个RegionFactory的实现。

We’ll add the Ehcache region factory implementation to the classpath with the following Maven dependency:

我们将在classpath中添加Ehcache区域工厂的实现,其Maven依赖关系如下。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
    <version>5.2.2.Final</version>
</dependency>

We can take a look here for the latest version of hibernate-ehcache. However, we need to make sure that the hibernate-ehcache version is equal to the Hibernate version we’re using in our project (e.g. if we use hibernate-ehcache 5.2.2.Final, like in this example, then the version of Hibernate should also be 5.2.2.Final).

我们可以在这里看一下hibernate-ehcache的最新版本。然而,我们需要确保hibernate-ehcache的版本与我们项目中使用的Hibernate版本相同(e.g.如果我们使用hibernate-ehcache 5.2.2.Final,像这个例子,那么Hibernate的版本也应该是5.2.2.Final)。

The hibernate-ehcache artifact has a dependency on the Ehcache implementation itself, which is transitively included in the classpath as well.

hibernate-ehcache工件对Ehcache实现本身有依赖性,它也被包含在classpath中。

4. Enabling Second-Level Caching

4.启用第二级缓存

With the following two properties, we’ll tell Hibernate that L2 caching is enabled, and give it the name of the region factory class:

通过以下两个属性,我们将告诉Hibernate,L2缓存已经启用,并给它一个区域工厂类的名字。

hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory

For example, in persistence.xml, it would look like:

例如,在persistence.xml中,它将看起来像。

<properties>
    ...
    <property name="hibernate.cache.use_second_level_cache" value="true"/>
    <property name="hibernate.cache.region.factory_class" 
      value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
    ...
</properties>

To disable second-level caching (say for debugging purposes), we just set the hibernate.cache.use_second_level_cache property to false.

要禁用二级缓存(比如出于调试目的),我们只需将hibernate.cache.use_second_level_cache属性设置为false。

5. Making an Entity Cacheable

5.使实体可被缓存

In order to make an entity eligible for second-level caching, we’ll annotate it with the Hibernate specific @org.hibernate.annotations.Cache annotation, and specify a cache concurrency strategy.

为了使实体有资格进行二级缓存,我们将用Hibernate特定的@org.hibernate.annotations.Cache注解来注释它,并指定一个缓存并发策略

Some developers consider it a good convention to add the standard @javax.persistence.Cacheable annotation as well (although not required by Hibernate), so an entity class implementation might look like this:

一些开发者认为将标准的@javax.persistence.Cacheable注解也添加进去是一个很好的惯例(尽管Hibernate并不要求),因此一个实体类的实现可能是这样的。

@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Foo {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private long id;

    @Column(name = "NAME")
    private String name;
    
    // getters and setters
}

For each entity class, Hibernate will use a separate cache region to store state of instances for that class. The region name is the fully qualified class name.

对于每个实体类,Hibernate将使用一个单独的缓存区域来存储该类实例的状态。区域名称是完全合格的类名称。

For example, Foo instances are stored in a cache named com.baeldung.hibernate.cache.model.Foo in Ehcache.

例如,Foo实例被存储在Ehcache中一个名为com.baeldung.hibernate.cache.model.Foo的缓存中。

To verify that caching is working, we can write a quick test:

为了验证缓存是否有效,我们可以写一个快速测试。

Foo foo = new Foo();
fooService.create(foo);
fooService.findOne(foo.getId());
int size = CacheManager.ALL_CACHE_MANAGERS.get(0)
  .getCache("com.baeldung.hibernate.cache.model.Foo").getSize();
assertThat(size, greaterThan(0));

Here we use Ehcache API directly to verify that the com.baeldung.hibernate.cache.model.Foo cache isn’t empty after we load a Foo instance.

这里我们直接使用Ehcache API来验证com.baeldung.hibernate.cache.model.Foo缓存在我们加载一个Foo实例后是否为空。

We could also enable the logging of SQL generated by Hibernate, and invoke fooService.findOne(foo.getId()) multiple times in the test to verify that the select statement for loading Foo is printed only once (the first time), meaning that in subsequent calls, the entity instance is fetched from the cache.

我们还可以启用Hibernate生成的SQL日志,并在测试中多次调用fooService.findOne(foo.getId()),以验证加载Fooselect语句只被打印一次(第一次),这意味着在后续调用中,实体实例是从缓存中获取的。

6. Cache Concurrency Strategy

6.缓存并发策略

Based on use cases, we’re free to pick one of the following cache concurrency strategies:

根据使用情况,我们可以自由选择以下缓存并发策略之一。

  • READ_ONLY: Used only for entities that never change (exception is thrown if an attempt to update such an entity is made). It’s very simple and performative. It’s suitable for static reference data that doesn’t change.
  • NONSTRICT_READ_WRITE: Cache is updated after the transaction that changed the affected data has been committed. Thus, strong consistency isn’t guaranteed, and there’s a small time window in which stale data may be obtained from the cache. This kind of strategy is suitable for use cases that can tolerate eventual consistency.
  • READ_WRITE: This strategy guarantees strong consistency, which it achieves by using ‘soft’ locks. When a cached entity is updated, a soft lock is stored in the cache for that entity as well, which is released after the transaction is committed. All concurrent transactions that access soft-locked entries will fetch the corresponding data directly from the database.
  • TRANSACTIONAL: Cache changes are done in distributed XA transactions. A change in a cached entity is either committed or rolled back in both the database and cache in the same XA transaction.

7. Cache Management

7.缓存管理

If expiration and eviction policies aren’t defined, the cache could grow indefinitely, and eventually consume all of the available memory. In most cases, Hibernate leaves cache management duties like these to cache providers, as they are indeed specific to each cache implementation.

如果没有定义过期和驱逐策略,缓存可能会无限增长,并最终消耗所有的可用内存。在大多数情况下,Hibernate将类似这样的缓存管理职责留给缓存提供者,因为它们确实是每个缓存实现的具体内容。

For example, we could define the following Ehcache configuration to limit the maximum number of cached Foo instances to 1000:

例如,我们可以定义以下Ehcache配置,将缓存的Foo实例的最大数量限制在1000个。

<ehcache>
    <cache name="com.baeldung.persistence.model.Foo" maxElementsInMemory="1000" />
</ehcache>

8. Collection Cache

8.收集缓存

Collections aren’t cached by default, and we need to explicitly mark them as cacheable:

集合在默认情况下是不被缓存的,我们需要明确地把它们标记为可缓存。

@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Foo {

    ...

    @Cacheable
    @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    @OneToMany
    private Collection<Bar> bars;

    // getters and setters
}

9. Internal Representation of Cached State

9.缓存状态的内部表述

Entities aren’t stored in the second-level cache as Java instances, but rather in their disassembled (hydrated) state:

实体不是以Java实例的形式存储在二级缓存中,而是以其拆解的(hydrated)状态存储。

  • Id (primary key) isn’t stored (it’s stored as part of the cache key)
  • Transient properties aren’t stored
  • Collections aren’t stored (see below for more details)
  • Non-association property values are stored in their original form
  • Only id (foreign key) is stored for ToOne associations

This depicts the general Hibernate second-level cache design, where the cache model reflects the underlying relational model, which is space-efficient and makes it easy to keep the two synchronized.

这描绘了一般的Hibernate二级缓存设计,其中缓存模型反映了底层的关系模型,这很节省空间,而且很容易保持两者的同步。

9.1. Internal Representation of Cached Collections

9.1.缓存集合的内部表示法

We already mentioned that we have to explicitly indicate that a collection (OneToMany or ManyToMany association) is cacheable, otherwise it isn’t cached.

我们已经提到,我们必须明确指出一个集合(OneToManyManyToMany关联)是可缓存的,否则它就不会被缓存。

Hibernate actually stores collections in separate cache regions, one for each collection. The region name is a fully qualified class name, plus the name of a collection property (for example, com.baeldung.hibernate.cache.model.Foo.bars). This gives us the flexibility to define separate cache parameters for collections, e.g. eviction/expiration policy.

Hibernate实际上将集合存储在独立的缓存区域中,每个集合一个。区域名称是一个完全合格的类名,加上一个集合属性的名称(例如,com.baeldung.hibernate.cache.model.Foo.bar)。这让我们可以灵活地给集合定义单独的缓存参数,e.g.驱逐/失效策略。

It’s also important to mention that only the ids of entities contained in a collection are cached for each collection entry. This means that in most cases, it’s a good idea to make the contained entities cacheable as well.

同样重要的是,对于每个集合条目,只有包含在集合中的实体的id被缓存。这意味着在大多数情况下,让所包含的实体也能被缓存是个好主意。

10. Cache Invalidation for HQL DML-Style Queries and Native Queries

10.HQL DML式查询和本地查询的缓存无效

When it comes to DML-style HQL (insert, update and delete HQL statements), Hibernate is able to determine which entities are affected by such operations:

当涉及到DML风格的HQL(insertupdatedelete HQL语句)时,Hibernate能够确定哪些实体受到此类操作的影响。

entityManager.createQuery("update Foo set … where …").executeUpdate();

In this case, all Foo instances are evicted from the L2 cache, while the other cached content remains unchanged.

在这种情况下,所有的Foo实例被从二级缓存中驱逐,而其他的缓存内容保持不变。

However, when it comes to native SQL DML statements, Hibernate can’t guess what’s being updated, so it invalidates the entire second level cache:

然而,当涉及到本地SQL DML语句时,Hibernate无法猜测正在更新的内容,所以它使整个二级缓存失效。

session.createNativeQuery("update FOO set … where …").executeUpdate();

This is probably not what we want. The solution is to tell Hibernate which entities are affected by native DML statements, so that it can evict only the entries related to Foo entities:

这可能不是我们想要的结果。解决方案是告诉Hibernate哪些实体会受到本地DML语句的影响,这样它就可以只驱逐与Foo实体相关的条目。

Query nativeQuery = entityManager.createNativeQuery("update FOO set ... where ...");
nativeQuery.unwrap(org.hibernate.SQLQuery.class).addSynchronizedEntityClass(Foo.class);
nativeQuery.executeUpdate();

We have to fall back to Hibernate native SQLQuery API, as this feature isn’t yet defined in JPA.

我们必须回到Hibernate本地的SQLQuery API,因为这个功能还没有在JPA中定义。

Note that the above applies only to DML statements (insert, update, delete, and native function/procedure calls). Native select queries don’t invalidate the cache.

请注意,上述内容只适用于DML语句(insert, update, delete,和本地函数/程序调用)。本地select查询不会使缓存失效。

11. Query Cache

11.查询缓存

We can also cache the results of HQL queries. This is useful if we frequently execute a query on entities that rarely change.

我们还可以缓存HQL查询的结果。如果我们经常对很少变化的实体执行查询,这很有用。

To enable the query cache, we’ll set the value of the hibernate.cache.use_query_cache property to true:

为了启用查询缓存,我们将设置hibernate.cache.use_query_cache属性的值为true

hibernate.cache.use_query_cache=true

For each query, we have to explicitly indicate that the query is cacheable (via an org.hibernate.cacheable query hint):

对于每个查询,我们必须明确指出该查询是可缓存的(通过org.hibernate.cacheable查询提示)。

entityManager.createQuery("select f from Foo f")
  .setHint("org.hibernate.cacheable", true)
  .getResultList();

11.1. Query Cache Best Practices

11.1.查询缓存的最佳实践

Here are a some guidelines and best practices related to query caching:

以下是与查询缓存有关的一些准则和最佳做法

  • As is the case with collections, only the ids of entities returned as a result of a cacheable query are cached. Therefore, we strongly recommend enabling a second-level cache for such entities.
  • There’s one cache entry per each combination of query parameter values (bind variables) for each query, so queries for which we expect lots of different combinations of parameter values aren’t good candidates for caching.
  • Queries that involve entity classes for which there are frequent changes in the database aren’t good candidates for caching either because they will be invalidated whenever there’s a change related to any of the entity classed participating in the query, regardless whether the changed instances are cached as part of the query result or not.
  • By default, all query cache results are stored in the org.hibernate.cache.internal.StandardQueryCache region. As with entity/collection caching, we can customize cache parameters for this region to define eviction and expiration policies according to our needs. For each query, we can also specify a custom region name in order to provide different settings for different queries.
  • For all tables that are queried as part of cacheable queries, Hibernate keeps last update timestamps in a separate region named org.hibernate.cache.spi.UpdateTimestampsCache. Being aware of this region is very important if we use query caching because Hibernate uses it to verify that cached query results aren’t stale. The entries in this cache must not be evicted/expired as long as there are cached query results for the corresponding tables in the query results regions. It’s best to turn off automatic eviction and expiration for this cache region, as it doesn’t consume lots of memory anyway.

12. Conclusion

12.结论

In this article, we learned how to set up a Hibernate second-level cache. Hibernate is fairly easy to configure and use, making second-level cache utilization transparent to the application business logic.

在这篇文章中,我们学习了如何设置Hibernate的二级缓存。Hibernate的配置和使用相当简单,使二级缓存的使用对应用程序的业务逻辑透明。

The implementation of this article is available over on Github. This is a Maven based project, so it should be easy to import and run as it is.

本文的实现可通过Github上的。这是一个基于Maven的项目,所以应该很容易导入并按原样运行。