Hibernate Envers AuditReader调用缓存实现方案咨询
Hey there! Let's tackle this caching issue with Hibernate Envers. I see you're already using Hazelcast as your second-level cache provider, but Envers queries are still hitting the DB—no surprise, since Envers doesn't automatically leverage the second-level cache for its audit queries or entities unless you explicitly configure it. Here's a step-by-step solution tailored to your use case:
一、 Enable Caching for Envers Audit Entities
First off, Envers auto-generates audit entities (like EntityName_AUD for your audited entities) that don't have caching enabled by default. You need to explicitly enable this so Hazelcast can cache these audit records:
Option 1: Annotate Your Original Entities
Add cache annotations directly to your audited entities—Envers will inherit this configuration for the corresponding audit entities:
@Entity @Audited @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) // Perfect for creation revisions (ADD operations never change) public class YourEntity { // Your entity fields and methods }
For entities where you need to cache update revisions, use CacheConcurrencyStrategy.READ_WRITE instead.
Option 2: Global Configuration in persistence.xml
If you prefer a global setup, add these properties to your persistence unit config:
<persistence-unit name="Vedantas.PU_NAME"> <!-- Existing config --> <properties> <!-- Enable second-level and query cache --> <property name="hibernate.cache.use_second_level_cache" value="true"/> <property name="hibernate.cache.use_query_cache" value="true"/> <property name="hibernate.cache.region.factory_class" value="com.hazelcast.hibernate.HazelcastCacheRegionFactory"/> <!-- Set default cache strategy for Envers audit entities --> <property name="org.hibernate.envers.cache.strategy" value="read-only"/> </properties> </persistence-unit>
二、 Enable Query Caching for AuditReader Queries
Even with audit entities cached, Envers queries won't use the cache unless you mark them as cacheable. Update your AuditQuery calls to enable query caching:
Modify getCreationRevisionFor
@Transactional(readOnly = true) public OperationCtx getCreationRevisionFor(Class<?> aClass, long id) { auditReader = AuditReaderFactory.get(entityManager); log.info(new LogModel("getting creation revision for entity class: {}; id: {}").toString(), aClass.getName(), id); final Object[] array = (Object[]) auditReader.createQuery() .forRevisionsOfEntity(aClass, false, false) .add(AuditEntity.revisionType().eq(RevisionType.ADD)) .add(AuditEntity.id().eq(id)) .setMaxResults(1) .setCacheable(true) // Enable query caching .setCacheRegion("envers-creation-queries") // Assign a dedicated cache region .getSingleResult(); return ((OperationCtx) array[1]); }
Modify getLastRevisionFor
@Transactional(readOnly = true) public OperationCtx getLastRevisionFor(Class<?> aClass, long id) { log.info(new LogModel("getting last revision for entity class: {}; id: {}").toString(), aClass.getName(), id); auditReader = AuditReaderFactory.get(entityManager); final Object[] array = (Object[]) auditReader.createQuery() .forRevisionsOfEntity(aClass, false, false) .add(AuditEntity.id().eq(id)) .add(AuditEntity.revisionNumber().maximize() .computeAggregationInInstanceContext() ) .addOrder(AuditEntity.revisionNumber().desc()) .setMaxResults(1) .setCacheable(true) // Enable query caching .setCacheRegion("envers-last-revision-queries") // Dedicated cache region .getSingleResult(); OperationCtx lastchg = (OperationCtx) array[1]; OperationCtx creation = getCreationRevisionFor(aClass, id); if (creation.getId() == lastchg.getId()) return new OperationCtx(); return lastchg; }
三、 Add Declarative Business-Level Caching (Recommended)
Since creation revisions never change and last revisions only change when the entity is updated, using Spring Cache (with Hazelcast as the backend) gives you full control over cache lifecycle:
1. Configure Spring Cache with Hazelcast
- Add
@EnableCachingto your Spring configuration class - Set up
HazelcastCacheManager(since you're already using Hazelcast for Hibernate, this integration is straightforward)
2. Cache Creation Revisions
Annotate getCreationRevisionFor to cache its results permanently (since creation data never changes):
@Transactional(readOnly = true) @Cacheable(value = "creationRevisions", key = "#aClass.name + ':' + #id") public OperationCtx getCreationRevisionFor(Class<?> aClass, long id) { // Existing implementation }
3. Cache Last Revisions + Evict on Entity Update
Annotate getLastRevisionFor to cache results, then add an eviction annotation to your entity update methods to clear stale cache entries:
@Transactional(readOnly = true) @Cacheable(value = "lastRevisions", key = "#aClass.name + ':' + #id") public OperationCtx getLastRevisionFor(Class<?> aClass, long id) { // Existing implementation } // Example entity update method in your service @Transactional @CacheEvict(value = "lastRevisions", key = "#entity.getClass().name + ':' + #entity.id") public void updateEntity(YourEntity entity) { entityManager.merge(entity); }
4. Automatically Benefit in setManipulationCtx
Your setManipulationCtx method calls the two cached methods, so it will automatically use cached data on subsequent calls—no changes needed here!
四、 Key Notes
- Ensure Hazelcast's cache region configurations align with your needs: creation revisions can have a permanent TTL, while last revisions can either use a reasonable TTL or rely solely on
@CacheEvict. - Double-check that
hibernate.cache.use_query_cache=trueis set, otherwisesetCacheable(true)on AuditQueries won't work. - If using Spring Boot, Hazelcast integration is even simpler—just add the dependency and configure cache properties in
application.properties.
内容的提问来源于stack exchange,提问作者greengold

