温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何使用Spring和Hibernate自定义审计日志

发布时间:2022-02-28 11:21:31 来源:亿速云 阅读:160 作者:小新 栏目:开发技术

这篇文章主要介绍如何使用Spring和Hibernate自定义审计日志,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

如果您需要对所有数据库操作进行自动审计,并且您正在使用 Hibernate……您应该使用Enversspring data jpa auditing。但是如果由于某些原因您不能使用 Envers,您可以使用 hibernate 事件侦听器和 spring 事务同步来实现类似的功能。

首先,从事件监听器开始。您应该捕获所有插入、更新和删除操作。但是有一个棘手的问题——如果您出于任何原因需要刷新会话,则不能使用传递给事件侦听器的会话直接执行该逻辑。在我的情况下,我必须获取一些数据,并且 hibernate 开始向我抛出异常(“id 为 null”)。多个来源确认您不应在事件侦听器中与数据库交互。因此,您应该存储事件以供以后处理。您可以将侦听器注册为 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- 它是一个自定义标记注释(retention=runtime, target=type),您可以将其放在实体之上。

由于我有 spring 可供我使用的AuditLogServiceData,所以在我的示例上我决定使用 spring:

/**
 * {@link AuditLogServiceStores} stores here audit log information It records all
 * changes to the entities in spring transaction synchronizaton resources, which
 * are in turn stored as {@link ThreadLocal} variables for each thread. Each thread
 * /transaction is using own copy of this data.
 */
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);
        }
    }
 
    public void clear() {
       // unbind all resources
    }
}

除了存储事件之外,我们还需要存储正在执行操作的用户。为了得到它,我们需要提供一个方法参数级别的注释来指定一个参数。在我的例子中的注释被称为AuditLogActor(retention=runtime, type=parameter)

现在剩下的是处理事件的代码。我们希望在提交当前事务之前执行此操作。如果事务在提交时失败,审计条目插入也将失败。我们用一点 AOP 来做到这一点:

@Aspect
@Component
class AuditLogStoringAspectextends 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
        }
    }
 
    @Override
    public void afterCompletion(int status) {
    // we have to unbind all resources as spring does not do that automatically
        AuditLogServiceData.clear();
     }

就我而言,我不得不注入额外的服务,而 spring 抱怨相互依赖的 bean,所以我改为使用applicationContext.getBean(FooBean.class). 注意:确保您的方面被 spring 捕获 - 通过自动扫描或通过 xml/java-config 显式注册它。

因此,经过审计的调用将如下所示:

@Transactional
public void saveFoo(FooRequest request,@AuditLogActor Long actorId) { .. }

总结一下:hibernate 事件监听器将所有插入、更新和删除事件存储为 Spring 事务同步资源。一个方面向 spring 注册一个事务“回调”,它在每个事务提交之前被调用。在那里处理所有事件并插入相应的审计日志条目。

这是非常基本的审计日志,它可能在收集处理方面存在问题,而且它肯定没有涵盖所有用例。但它比手动审计日志处理要好得多,并且在许多系统中,审计日志是强制性功能。

以上是“如何使用Spring和Hibernate自定义审计日志”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注亿速云行业资讯频道!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI