如何自定义Hibernate EnVers?实体审计库定制需求与方案咨询
Great question! Let's walk through how you can extend Hibernate EnVers to meet your exact needs—per-entity audit tables and JSON-based incremental diff logs instead of full snapshots.
First, a quick note: EnVers actually already creates a separate audit table for each entity (named like your_entity_AUD by default), so that requirement is partially covered right out of the box. The main work is modifying its behavior to store diffs instead of full entity snapshots. Here's an actionable plan:
Customizing Hibernate EnVers for JSON Diff Audit Logs
1. Implement a Custom AuditStrategy
The core of EnVers' audit logic is controlled by the AuditStrategy interface. We'll extend the default AbstractAuditStrategy to compute and store JSON diffs instead of full entity snapshots:
- Create a class like
JsonDiffAuditStrategythat extendsAbstractAuditStrategy. - Override the
performmethod, which handles writing audit records to the database. - In this method, fetch the current entity state and its previous state (from EnVers' history store).
- Use a JSON library (like Jackson with the
jackson-diffmodule) to generate a structured diff between the two states. - Replace the full entity field insert with the JSON diff string, paired with standard revision metadata (revision ID, revision type: insert/update/delete).
Sample code snippet:
public class JsonDiffAuditStrategy extends AbstractAuditStrategy { private final ObjectMapper objectMapper = new ObjectMapper(); private final JsonDiff diffGenerator = JsonDiff.builder().build(); @Override public void perform(Session session, String entityName, AuditConfiguration auditCfg, Serializable id, Object data, Object revision) { // Retrieve the previous state of the entity from EnVers history Object previousState = getPreviousEntityState(session, auditCfg, entityName, id, revision); // Generate JSON diff JsonNode currentEntityNode = objectMapper.valueToTree(data); JsonNode diffNode = previousState != null ? diffGenerator.diff(objectMapper.valueToTree(previousState), currentEntityNode) : currentEntityNode; // For new entities, store full state as initial record String diffJson = objectMapper.writeValueAsString(diffNode); // Build and persist the audit record with diff data AuditRecord auditRecord = new AuditRecord(id, ((RevisionEntity) revision).getId(), getRevisionType(data), diffJson); session.persist(auditRecord); } // Helper method to fetch previous entity state private Object getPreviousEntityState(Session session, AuditConfiguration auditCfg, String entityName, Serializable id, Object revision) { // Use EnVers' query API to fetch the most recent prior state AuditQuery query = auditCfg.getAuditReaderFactory().get(session).createQuery() .forEntitiesAtRevision(entityName, ((RevisionEntity) revision).getId() - 1); query.add(AuditEntity.id().eq(id)); return query.getSingleResultOrNull(); } }
2. Customize Audit Table Structure
By default, EnVers creates audit tables that mirror the original entity's fields. We need to adjust this to store only revision metadata plus the JSON diff:
- Use
@AuditTableon your entities to explicitly define audit table names (optional but recommended for consistency). - Implement a
CustomEntityDefinitionListenerto intercept EnVers' audit entity creation process. This lets you replace all entity-specific fields with a singlejson_diffcolumn (useTEXTor database-nativeJSONtype, depending on your DB).
Example listener code:
public class JsonDiffAuditEntityListener implements CustomEntityDefinitionListener { @Override public void customize(EntityDefinition entityDefinition, AuditConfiguration auditCfg) { // Clear default mirrored fields entityDefinition.getAttributes().clear(); // Add mandatory revision columns entityDefinition.addAttribute( AttributeDefinition.builder() .name("rev") .type(Long.class) .column(ColumnDefinition.builder().name("rev").build()) .build() ); entityDefinition.addAttribute( AttributeDefinition.builder() .name("revtype") .type(Integer.class) .column(ColumnDefinition.builder().name("revtype").build()) .build() ); // Add JSON diff column entityDefinition.addAttribute( AttributeDefinition.builder() .name("jsonDiff") .type(String.class) .column(ColumnDefinition.builder().name("json_diff").sqlType("JSON").build()) .build() ); // Add entity ID column (to link audit records to source entity) entityDefinition.addAttribute( AttributeDefinition.builder() .name("entityId") .type(Serializable.class) .column(ColumnDefinition.builder().name("entity_id").build()) .build() ); } }
3. Configure EnVers to Use Your Custom Components
Update your Hibernate configuration to register the custom strategy and listener:
Add these properties to your persistence.xml or Hibernate properties file:
org.hibernate.envers.audit_strategy=com.yourpackage.JsonDiffAuditStrategy org.hibernate.envers.custom_entity_definition_listener=com.yourpackage.JsonDiffAuditEntityListener
4. Handle Edge Cases
- First-time inserts: For new entities, store the full entity JSON as the initial audit record (since there's no prior state to diff against).
- Deletions: When deleting an entity, you might want to store the final state or a clear deletion marker in the JSON diff for auditing clarity.
- Performance: Diff computation for large entities can add overhead. Consider caching recent entity states or optimizing diff logic to only track fields marked with a custom annotation (e.g.,
@AuditDiff).
Alternative Tools to Consider
If extending EnVers feels too involved, you might explore:
- Custom Hibernate Interceptor: A lightweight approach where you implement a
Interceptorto capture entity changes, compute diffs, and write to your own audit tables. This gives full control but requires more boilerplate. - Spring Data Envers: Built on top of EnVers with Spring Data integration, but still uses full snapshots by default—you'd still need to add the same custom diff logic.
内容的提问来源于stack exchange,提问作者Rad




