这篇文章将介绍如何设置Hibernate二级和查询缓存,它们如何工作以及最常见的陷阱。
休眠二级缓存是用于存储实体数据的应用程序级缓存。 查询缓存是一个单独的缓存,仅存储查询结果。
这两个缓存实际上是并存的,因为在很多情况下,我们都希望在没有其他缓存的情况下使用它们。 如果使用得当,这些缓存可以通过减少命中数据库的SQL语句的数量,以透明的方式提高性能。
第二级缓存如何工作?
第二级缓存存储实体数据,但不存储实体本身。 数据以“脱水”格式存储,该格式看起来像哈希图,其中键是实体ID,而值是原始值列表。
这是有关二级缓存内容的外观的示例:
*-----------------------------------------*
| Person Data Cache |
|-----------------------------------------|
| 1 -> [ "John" , "Q" , "Public" , null ] |
| 2 -> [ "Joey" , "D" , "Public" , 1 ] |
| 3 -> [ "Sara" , "N" , "Public" , 1 ] |
*-----------------------------------------*
当通过Id从数据库entityManager.find()
例如,使用entityManager.find()
从对象加载对象时,或者遍历惰性初始化关系时,将填充第二级缓存。
查询缓存如何工作?
查询高速缓存在概念上看起来像一个哈希图,其中键由查询文本和参数值组成,并且值是与查询匹配的实体ID的列表:
*----------------------------------------------------------*
| Query Cache |
|----------------------------------------------------------|
| ["from Person where firstName=?", ["Joey"] ] -> [1, 2] ] |
*----------------------------------------------------------*
有些查询不返回实体,而是仅返回原始值。 在那些情况下,值本身将存储在查询缓存中。 执行可缓存的JPQL / HQL查询时,将填充查询缓存。
这两个缓存之间有什么关系?
如果正在执行的查询先前已缓存了结果,则不会有SQL语句发送到数据库。 而是从查询缓存中检索查询结果,然后使用缓存的实体标识符访问第二级缓存。
如果第二级缓存包含给定ID的数据,它将重新水化实体并返回它。 如果第二级缓存不包含该特定ID的结果,则将发出SQL查询以从数据库中加载实体。
如何在应用程序中设置两个缓存
第一步是在类路径中包含hibernate-ehcache
jar:
<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-ehcache</artifactId><version>SOME-HIBERNATE-VERSION</version>
</dependency>
需要将以下参数添加到EntityManagerFactory
或SessionFactory
的配置中:
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="net.sf.ehcache.configurationResourceName">/your-cache-config.xml</prop>
首选使用EhCacheRegionFactory
而不是SingletonEhCacheRegionFactory
。 使用EhCacheRegionFactory
意味着Hibernate将为Hibernate缓存创建单独的缓存区域,而不是尝试重用应用程序中其他位置定义的缓存区域。
下一步是在文件your-cache-config.xml
配置缓存区域设置:
<?xml version="1.0" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"updateCheck="false"xsi:noNamespaceSchemaLocation="ehcache.xsd" name="yourCacheManager"><diskStore path="java.io.tmpdir"/><cache name="yourEntityCache"maxEntriesLocalHeap="10000"eternal="false"overflowToDisk="false"timeToLiveSeconds="86400" /><cache name="org.hibernate.cache.internal.StandardQueryCache"maxElementsInMemory="10000"eternal="falsetimeToLiveSeconds="86400"overflowToDisk="false"memoryStoreEvictionPolicy="LRU" /><defaultCachemaxElementsInMemory="10000"eternal="false"timeToLiveSeconds="86400"overflowToDisk="false"memoryStoreEvictionPolicy="LRU" />
</ehcache>
如果未指定缓存设置,则采用默认设置,但是最好避免这种情况。 通过在ehcache
元素中填充name
属性,确保为缓存命名。
为缓存指定一个名称可以防止它使用默认名称,该名称可能已经在应用程序的其他位置使用过。
使用二级缓存
现在可以使用二级缓存了。 为了缓存实体,请使用@org.hibernate.annotations.Cache
注释对它们进行注释:
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY, region="yourEntityCache")
public class SomeEntity {...
}
关联也可以由二级缓存进行缓存,但是默认情况下不这样做。 为了启用关联的缓存,我们需要将@Cache
应用于关联本身:
@Entity
public class SomeEntity {@OneToMany@Cache(usage=CacheConcurrencyStrategy.READ_ONLY,region="yourCollectionRegion")private Set<OtherEntity> other;
}
使用查询缓存
配置查询缓存后,默认情况下尚未缓存任何查询。 需要将查询明确标记为已缓存,例如,这是如何将命名查询标记为已缓存:
@NamedQuery(name="account.queryName",query="select acct from Account ...",hints={@QueryHint(name="org.hibernate.cacheable",value="true")}
})
这是将条件查询标记为已缓存的方法:
List cats = session.createCriteria(Cat.class).setCacheable(true).list();
下一节将介绍一些在尝试设置这两个缓存时可能遇到的陷阱。 这些行为按设计工作,但仍然令人惊讶。
陷阱1 –查询缓存会降低性能,导致大量查询
如果将缓存的查询结果配置为比查询返回的缓存的实体更频繁地过期,则会发生两个缓存的工作方式的有害副作用。
如果查询已缓存结果,它将返回实体ID的列表,然后针对第二级缓存进行解析。 如果具有这些ID的实体未配置为可缓存或它们已过期,则每个实体ID的选择将命中数据库。
例如,如果一个缓存的查询返回了1000个实体ID,而第二级缓存中没有这些实体,则将对数据库发出1000个ID选择。
该问题的解决方案是将查询结果到期配置为与查询返回的实体的到期保持一致。
陷阱2 –与
当前无法为同一父实体的不同子类指定不同的缓存策略。
例如,这将不起作用:
@Entity
@Inheritance
@Cache(CacheConcurrencyStrategy.READ_ONLY)
public class BaseEntity {...
}@Entity
@Cache(CacheConcurrencyStrategy.READ_WRITE)
public class SomeReadWriteEntity extends BaseEntity {...
}@Entity
@Cache(CacheConcurrencyStrategy.TRANSACTIONAL)
public class SomeTransactionalEntity extends BaseEntity {...
}
在这种情况下,仅考虑父类的@Cache
注释,并且所有具体实体都具有READ_ONLY
并发策略。
陷阱3 –使用基于单例的缓存时,缓存设置将被忽略
建议将缓存区域工厂配置为EhCacheRegionFactory
,并通过net.sf.ehcache.configurationResourceName
指定ehcache配置。
此区域工厂的替代方法是SingletonEhCacheRegionFactory
。 对于该区域工厂,使用缓存名称作为查找键将缓存区域存储为单例。
单例区域工厂的问题在于,如果应用程序的另一部分已经在单例中注册了具有默认名称的缓存,则这将导致忽略通过net.sf.ehcache.configurationResourceName
传递的ehcache配置文件。
结论
如果设置正确,则二级缓存和查询缓存非常有用,但是要记住一些陷阱,以免发生意外行为。 总而言之,这是一个透明工作的功能,如果使用得当,可以显着提高应用程序的性能。
翻译自: https://www.javacodegeeks.com/2014/06/pitfalls-of-the-hibernate-second-level-query-caches.html