Spring-Mongo集成Javers:如何在onSaveAll前过滤审计数据?
onSaveAllExecuted() Audit Got it, I’ve faced a similar requirement before when working with Javers and Spring Data MongoDB. Here are a few practical solutions you can try to filter data before Javers’ audit kicks in:
1. Custom Repository Implementation (Most Straightforward)
Instead of trying to intercept the Javers aspect directly, override the saveAll method in your repository to filter entities before they reach the Javers audit logic. This gives you full control over which entities get audited.
Example Code:
First, define a custom repository interface:
public interface CustomizedAuditableRepository<T> { <S extends T> List<S> saveAll(Iterable<S> entities); }
Then implement it, adding your filtering logic:
public class CustomizedAuditableRepositoryImpl<T> implements CustomizedAuditableRepository<T> { private final MongoRepository<T, ?> baseRepository; private final MongoTemplate mongoTemplate; public CustomizedAuditableRepositoryImpl(MongoRepository<T, ?> baseRepository, MongoTemplate mongoTemplate) { this.baseRepository = baseRepository; this.mongoTemplate = mongoTemplate; } @Override public <S extends T> List<S> saveAll(Iterable<S> entities) { // Split entities into auditable and non-auditable groups Map<Boolean, List<S>> splitEntities = StreamSupport.stream(entities.spliterator(), false) .collect(Collectors.partitioningBy(this::shouldAudit)); // Save auditable entities via the base repository (triggers Javers audit) List<S> auditedSaved = baseRepository.saveAll(splitEntities.get(true)); // Save non-auditable entities directly with MongoTemplate (bypasses Javers) splitEntities.get(false).forEach(mongoTemplate::save); return Stream.concat(auditedSaved.stream(), splitEntities.get(false).stream()) .collect(Collectors.toList()); } private boolean shouldAudit(S entity) { // Add your custom filtering logic here // Example: Check if the entity doesn't have an "ignoreAudit" flag if (entity instanceof AuditableMarker) { return !((AuditableMarker) entity).isIgnoreAudit(); } return true; } }
Finally, extend your main repository interface with the custom one:
public interface UserRepository extends MongoRepository<User, String>, CustomizedAuditableRepository<User> { }
Pros: Direct control over data flow, no dependency on Javers' internal aspects.
Cons: Requires implementing this for each repository that needs filtering.
2. High-Priority Custom Aspect (Global Filtering)
If you want a global solution without modifying every repository, create a custom Spring AOP aspect that runs before Javers' JaversSpringDataAuditableRepositoryAspect. You can modify the saveAll method arguments to filter entities before Javers processes them.
Example Code:
@Aspect @Component @Order(Ordered.HIGHEST_PRECEDENCE) // Ensure this runs before Javers' aspect public class JaversPreFilterAspect { @Around("execution(* org.springframework.data.mongodb.repository.MongoRepository.saveAll(..)) && @annotation(org.javers.spring.annotation.JaversSpringDataAuditable)") public Object filterEntitiesBeforeAudit(ProceedingJoinPoint joinPoint) throws Throwable { Object[] args = joinPoint.getArgs(); // Check if the first argument is an Iterable of entities if (args.length > 0 && args[0] instanceof Iterable) { Iterable<?> originalEntities = (Iterable<?>) args[0]; // Filter entities based on your rules List<?> filteredEntities = StreamSupport.stream(originalEntities.spliterator(), false) .filter(this::isEligibleForAudit) .collect(Collectors.toList()); // Replace the original argument with filtered list args[0] = filteredEntities; } // Proceed with the modified arguments (Javers will process the filtered list) return joinPoint.proceed(args); } private boolean isEligibleForAudit(Object entity) { // Custom logic: e.g., exclude entities of a specific type return !(entity instanceof TemporaryEntity); } }
Pros: Applies filtering globally to all repositories annotated with @JaversSpringDataAuditable.
Cons: Relies on Spring AOP ordering, which could break if Javers changes its aspect's order in future versions.
3. Javers Custom Change Filter (Post-Change Generation)
If the above options don't work, you can intercept Javers' commit process to filter out unwanted changes after they're generated but before they're saved to the audit store. This is less efficient but useful if you need to filter based on change details (not just entity properties).
Example Code:
@Component public class FilteringJaversCommitListener implements CommitListener { @Override public void onCommit(Commit commit, Javers javers) { // Filter out changes that don't need to be audited List<Change> filteredChanges = commit.getChanges().stream() .filter(change -> shouldKeepChange(change)) .collect(Collectors.toList()); // If no changes left, skip saving the commit if (filteredChanges.isEmpty()) { return; } // Create a new commit with filtered changes and save it manually Commit filteredCommit = new Commit(commit.getMetadata(), filteredChanges); javers.getRepository().commit(filteredCommit); } private boolean shouldKeepChange(Change change) { // Example: Exclude changes to the "lastLogin" field if (change instanceof PropertyChange) { PropertyChange propertyChange = (PropertyChange) change; return !"lastLogin".equals(propertyChange.getPropertyName()); } return true; } }
Pros: Can filter based on specific change details (e.g., which field was modified).
Cons: Generates unnecessary changes first, which is less performant.
内容的提问来源于stack exchange,提问作者Ketan Nabera

