Spring Data Envers 支持有条件变动纪录的保存和查询

2023-10-15 15:15

本文主要是介绍Spring Data Envers 支持有条件变动纪录的保存和查询,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

数据审计是业务系统的一个基本能力,需要系统能够将关键数据的变动纪录都保存下来,并支持变动纪录的查询。

通过spring-data-envers可以很容易的实现数据变动纪录的保存和查询。

有些情况下,我们需要只保存满足特定条件的数据变动纪录,不满足条件的变动纪录不进行保存,例如只保存某个字段有值的变动纪录。

本文介绍支持有条件变动纪录的保存和查询的方法。

具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-data-envers-conditional

一、概述

可以通过 spring-data-envers 很容易的实现变动纪录的保存和查询,只需要增加几个注解就可以。但是要实现有条件的变动纪录的保存和查询就需要进行一些复杂的处理。

二、使用 spring-data-envers

首先引入 spring-data-envers 依赖。

在 build.gradle 中增加一行代码:

implementation 'org.springframework.data:spring-data-envers'

在实体类上增加 Audited 注解:

@Data
@Entity
@Audited
public class MyData {@Id@GeneratedValueprivate Long id;private String author;
}

Repository 扩展 RevisionRepository 方法:

public interface MyDataRepository extends JpaRepository<MyData, Long>, RevisionRepository<MyData, Long, Integer> {
}

通过以上3步操作,就添加好了变动纪录的保存功能,我们可以通过调用变动纪录查询方法确认变动纪录保存成功。

当 Repository 扩展 RevisionRepository 方法后,会有一个默认实现的 findRevisions 方法,我们可以直接调用:

public Revisions<Integer, MyData> findRevisions(Long id) {return myDataRepository.findRevisions(id);
}

最后我们可以执行完整的主体数据的保存,在控制台中打印变动纪录:

@Override
public void run(String... args) {MyData myData = new MyData();myData.setId(1L);myData.setAuthor("test");dbService.saveData(myData);dbService.findRevisions(myData.getId()).forEach(r -> System.out.println("revision: " + r.toString()));myData.setAuthor("newAuthor");dbService.saveData(myData);dbService.findRevisions(myData.getId()).forEach(r -> System.out.println("revision: " + r.toString()));
}

执行完程序后,可以看到两次保存数据的操作都可以查询到对应的变动纪录,并且变动纪录还通过 revisionType 显示了是插入还是更新操作:

revision: Revision 1 of entity MyData(id=1, author=test) - Revision metadata DefaultRevisionMetadata{entity=DefaultRevisionEntity(id = 1, revisionDate = Oct 15, 2023, 11:41:15 AM), revisionType=INSERT}
revision: Revision 2 of entity MyData(id=1, author=newAuthor) - Revision metadata DefaultRevisionMetadata{entity=DefaultRevisionEntity(id = 2, revisionDate = Oct 15, 2023, 11:41:16 AM), revisionType=UPDATE}

三、通过自定义 Event Listener 实现有条件的变动纪录的保存

在进行数据变动时, Envers 通过监听事件来进行对应的处理,总共有以下几个监听事件:

EventType.POST_INSERT
EventType.PRE_UPDATE
EventType.POST_UPDATE
EventType.POST_DELETE
EventType.POST_COLLECTION_RECREATE
EventType.PRE_COLLECTION_REMOVE
EventType.PRE_COLLECTION_UPDATE

每个监听事件都对应着特定的 Listener ,在本文实例中,我们期望当 author 的值被更新为空时,不保存变动纪录,我们可以通过自定义 PRE_UPDATE 和 POST_UPDATE 的Listener来实现。

因为框架提供了默认的Listener,因此自定义 Listener 只需要扩展默认的Listener,并加入我们自己的特有逻辑就可以。

MyEnversPostUpdateEventListenerImpl :

public class MyEnversPreUpdateEventListenerImpl extends EnversPreUpdateEventListenerImpl {public MyEnversPreUpdateEventListenerImpl(EnversService enversService) {super(enversService);}@Overridepublic boolean onPreUpdate(PreUpdateEvent event) {if (event.getEntity() instanceof MyData&& ((MyData) event.getEntity()).getAuthor() == null) {return false;}return super.onPreUpdate(event);}}

MyEnversPostUpdateEventListenerImpl:

public class MyEnversPostUpdateEventListenerImpl extends EnversPostUpdateEventListenerImpl {public MyEnversPostUpdateEventListenerImpl(EnversService enversService) {super(enversService);}@Overridepublic void onPostUpdate(PostUpdateEvent event) {if (event.getEntity() instanceof MyData && ((MyData) event.getEntity()).getAuthor() == null) {return;}super.onPostUpdate(event);}
}

在自定义 Listener 中,我们增加了 对于 author 字段是否为空的判断逻辑。

四、自定义 Event Listener 注册到系统中

自定义 Event Listener 完成后,我们还需要让框架执行我们自定义的 Listener, 而不是用默认的 Listener。

框架通过 EnversIntegrator 类注册的 Listener, 我们要做的是重新实现 EnversIntegrator , 在本实例中重新实现的类为 MyEnversIntegrator :

public class MyEnversIntegrator implements Integrator {@Overridepublic void integrate(Metadata metadata,BootstrapContext bootstrapContext,SessionFactoryImplementor sessionFactory) {final ServiceRegistry serviceRegistry = sessionFactory.getServiceRegistry();final EnversService enversService = serviceRegistry.getService(EnversService.class);final EventListenerRegistry listenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);listenerRegistry.addDuplicationStrategy(EnversListenerDuplicationStrategy.INSTANCE);if (enversService.getEntitiesConfigurations().hasAuditedEntities()) {listenerRegistry.appendListeners(EventType.POST_DELETE,new EnversPostDeleteEventListenerImpl(enversService));listenerRegistry.appendListeners(EventType.POST_INSERT,new EnversPostInsertEventListenerImpl(enversService));listenerRegistry.appendListeners(EventType.PRE_UPDATE,new MyEnversPreUpdateEventListenerImpl(enversService));listenerRegistry.appendListeners(EventType.POST_UPDATE,new MyEnversPostUpdateEventListenerImpl(enversService));listenerRegistry.appendListeners(EventType.POST_COLLECTION_RECREATE,new EnversPostCollectionRecreateEventListenerImpl(enversService));listenerRegistry.appendListeners(EventType.PRE_COLLECTION_REMOVE,new EnversPreCollectionRemoveEventListenerImpl(enversService));listenerRegistry.appendListeners(EventType.PRE_COLLECTION_UPDATE,new EnversPreCollectionUpdateEventListenerImpl(enversService));}}@Overridepublic void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {// nothing to do}
}

通过代码可以发现,我们只是修改了 PRE_UPDATE 和 POST_UPDATE 注册的 Listener , 其它事件的 Listener 仍然用框架默认的。

最后我们需要把我们实现的 MyEnversIntegrator 放到 META-INF/services/org.hibernate.integrator.spi.Integrator 这个配置文件中。

cn.springcamp.springdata.envers.MyEnversIntegrator

五、确认有条件变动纪录的保存是否生效

最后我们修改控制台打印程序,将 author 字段更新为 null 并保存,查看变动纪录里是否有这个更新操作的纪录。

增加保存代码 :

// won't generate audit record when author is null
myData.setAuthor(null);
dbService.saveData(myData);
dbService.findRevisions(myData.getId()).forEach(r -> System.out.println("revision: " + r.toString()));

执行程序并观察控制台打印内容:

revision: Revision 1 of entity MyData(id=1, author=test) - Revision metadata DefaultRevisionMetadata{entity=DefaultRevisionEntity(id = 1, revisionDate = Oct 15, 2023, 11:41:15 AM), revisionType=INSERT}
revision: Revision 2 of entity MyData(id=1, author=newAuthor) - Revision metadata DefaultRevisionMetadata{entity=DefaultRevisionEntity(id = 2, revisionDate = Oct 15, 2023, 11:41:16 AM), revisionType=UPDATE}

通过打印内容可以确认,author 字段更新为 null 的变动纪录没有被纪录,说明我们的处理是生效的。

这篇关于Spring Data Envers 支持有条件变动纪录的保存和查询的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/218338

相关文章

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

Java中注解与元数据示例详解

《Java中注解与元数据示例详解》Java注解和元数据是编程中重要的概念,用于描述程序元素的属性和用途,:本文主要介绍Java中注解与元数据的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参... 目录一、引言二、元数据的概念2.1 定义2.2 作用三、Java 注解的基础3.1 注解的定义3.2 内

vscode保存代码时自动eslint格式化图文教程

《vscode保存代码时自动eslint格式化图文教程》:本文主要介绍vscode保存代码时自动eslint格式化的相关资料,包括打开设置文件并复制特定内容,文中通过代码介绍的非常详细,需要的朋友... 目录1、点击设置2、选择远程--->点击右上角打开设置3、会弹出settings.json文件,将以下内

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

JavaScript中的isTrusted属性及其应用场景详解

《JavaScript中的isTrusted属性及其应用场景详解》在现代Web开发中,JavaScript是构建交互式应用的核心语言,随着前端技术的不断发展,开发者需要处理越来越多的复杂场景,例如事件... 目录引言一、问题背景二、isTrusted 属性的来源与作用1. isTrusted 的定义2. 为

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug

Java中Object类的常用方法小结

《Java中Object类的常用方法小结》JavaObject类是所有类的父类,位于java.lang包中,本文为大家整理了一些Object类的常用方法,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. public boolean equals(Object obj)2. public int ha

SpringBoot项目中Maven剔除无用Jar引用的最佳实践

《SpringBoot项目中Maven剔除无用Jar引用的最佳实践》在SpringBoot项目开发中,Maven是最常用的构建工具之一,通过Maven,我们可以轻松地管理项目所需的依赖,而,... 目录1、引言2、Maven 依赖管理的基础概念2.1 什么是 Maven 依赖2.2 Maven 的依赖传递机