定制Spring和Hibernate系统的审计Audit日志
如果你希望能够对所有数据库操作自动审计,推荐使用 Envers 或 spring data jpa auditing,但是如果因为一些原因不使用Envers,你可以使用Hibernate的事件监听器和Spring事务同步机制完成类似审计日志功能,本文主要演示这方面的技术。
使用事件监听器,你能截获所有的新增 修改和删除操作,但是有一点技巧在里面,如果你需要flush会话,你不能在事件监听器中和数据库交互,你应该存储事件以便日后处理,你能注册监听器作为一个Spring bean如下:
@Component public class AuditLogEventListener implements PostUpdateEventListener, PostInsertEventListener, PostDeleteEventListener { @Override public void onPostDelete(PostDeleteEvent event) { AuditedEntity audited = event.getEntity().getClass().getAnnotation(AuditedEntity.class); if (audited != null) { AuditLogServiceData.getHibernateEvents().add(event); } } @Override public void onPostInsert(PostInsertEvent event) { AuditedEntity audited = event.getEntity().getClass().getAnnotation(AuditedEntity.class); if (audited != null) { AuditLogServiceData.getHibernateEvents().add(event); } } @Override public void onPostUpdate(PostUpdateEvent event) { AuditedEntity audited = event.getEntity().getClass().getAnnotation(AuditedEntity.class); if (audited != null) { AuditLogServiceData.getHibernateEvents().add(event); } } @Override public boolean requiresPostCommitHanding(EntityPersister persister) { return true; // Envers sets this to true only if the entity is versioned. So figure out for yourself if that's needed } }
注意到AuditedEntity是一个定制的marker元注释(retention=runtime, target=type),你可以将它作为元注释用在你的实体上面。
在下面AuditServiceData类使用Spring的事务机制进行持久化:
public class AuditLogServiceData { private static final String HIBERNATE_EVENTS = "hibernateEvents"; @SuppressWarnings("unchecked") public static List<Object> getHibernateEvents() { if (!TransactionSynchronizationManager.hasResource(HIBERNATE_EVENTS)) { TransactionSynchronizationManager.bindResource(HIBERNATE_EVENTS, new ArrayList<>()); } return (List<Object>) TransactionSynchronizationManager.getResource(HIBERNATE_EVENTS); } public static Long getActorId() { return (Long) TransactionSynchronizationManager.getResource(AUDIT_LOG_ACTOR); } public static void setActor(Long value) { if (value != null) { TransactionSynchronizationManager.bindResource(AUDIT_LOG_ACTOR, value); } } }
为了存储事件,我们也要存储用户执行的动作,为了能获得这些,我们需要提供一个方法参数级别的元注释来设计参数,这个元注释称为AuditLogActor(retention=runtime, type=parameter) 。
现在剩余的是处理事件的代码,我们要在提交当前事务之前实现事件存储,如果事务失败,审计输入也会失败,这里使用AOP:
@Aspect @Component class AuditLogStoringAspect extends TransactionSynchronizationAdapter { @Autowired private ApplicationContext ctx; @Before("execution(* *.*(..)) && @annotation(transactional)") public void registerTransactionSyncrhonization(JoinPoint jp, Transactional transactional) { Logger.log(this).debug("Registering audit log tx callback"); TransactionSynchronizationManager.registerSynchronization(this); MethodSignature signature = (MethodSignature) jp.getSignature(); int paramIdx = 0; for (Parameter param : signature.getMethod().getParameters()) { if (param.isAnnotationPresent(AuditLogActor.class)) { AuditLogServiceData.setActor((Long) jp.getArgs()[paramIdx]); } paramIdx ++; } } @Override public void beforeCommit(boolean readOnly) { Logger.log(this).debug("tx callback invoked. Readonly= " + readOnly); if (readOnly) { return; } for (Object event : AuditLogServiceData.getHibernateEvents()) { // handle events, possibly using instanceof } }
这里注入另外的服务,需要激活Auto-scanning或显式注册到XML或Java-config中。
调用这个审计如下代码:
@Transactional public void saveFoo(FooRequest request, @AuditLogActor Long actorId) { .. }
总结:Hibernate的事件监听器存储所有插入 更新和删除事件作为Spring事务同步资源,一个aspect注册器是带有事务回调的Spring类,能够在事务提交之前被调用,当有操作事件发生时,响应的审计日志会新增一个记录。