JaVers:自动化数据审计

news2024/11/25 0:47:08

在开发应用程序时,我们经常需要存储有关数据如何随时间变化的信息。此信息可用于更轻松地调试应用程序并满足设计要求。在本文中,我们将讨论 JaVers 工具,该工具允许您通过记录数据库实体状态的更改来自动执行此过程。

Javers如何工作?

库的操作基于以 JSON 格式在专用表中存储有关业务数据实体的信息。JaVers允许您将这些信息存储在MongoDB,H2,PostgreSQL,MySQL,MariaDB,Oracle和Microsoft SQL Server等数据库中。

使用MongoDB时,JaVers会创建两个集合来存储审计数据:

  • jv_head_id– 用于存储包含commitId 最后一个值的文档的集合
  • jv_snapshots– 此集合包含有关由于创建、更新或删除操作而对业务实体所做的更改的详细信息。

另一方面,如果您决定使用其中一个关系数据库,JaVers 将创建以下表:

  • jv_global_id– 用于存储每个更改的唯一标识符的表
  • jv_commit – 包含有关数据修改的时间和作者信息的表
  • jv_commit_property – 一个附加表,允许您存储前一个表中数据的其他信息,例如用户的标识符和用户名。ID 和作为更改作者的用户的名称
  • jv_snapshot– 此表存储有关实体的哪些属性因给定操作而更改的信息,以及每个属性的值。

为了提供适当的算法来比较不同版本的对象,JaVers 对几种数据类型进行操作:实体、值对象、容器基元。

为每个类指示适当的数据类型,可以通过 3 种方式完成:

  • 明确地 – 使用寄存器...() 方法或对所选类添加适当的注释
  • 隐式 – 依靠 JaVers 根据类层次结构自动检测给定类的类型
  • 默认值 – 将所有类视为 ValueObjects

值得注意的是,JaVers 默认映射来自javax.persistence包的注解,这使得在实现我们的数据库实体时无需添加额外的注解。

实现

要开始使用 JaVers 工具,我们需要向项目添加适当的依赖项。根据我们将在其中存储审计数据的数据库,我们有两个选项可供选择:


  • https://mvnrepository.com/artifact/org.javers/javers-spring-boot-starter-sql
  • https://mvnrepository.com/artifact/org.javers/javers-spring-boot-starter-mongo

在本文中,我们将使用 PostgreSQL 数据库,因此我们在 pom 文件中包含以下依赖项:

<dependency> 
    <groupId>org.javers</groupId> 
    <artifactId>javers-spring-boot-starter-sql</artifactId> 
    <version>6.6.3</version> 
</dependency>

库的工作方式将以应用程序为例呈现,用于存储有关公司正在进行的项目的信息。我们应用程序的数据库实体的结构和实现如下:

Project.java

@Entity
public class Project {

    @Id
    @GeneratedValue
    private UUID id;

    @NotNull
    private String name;

    @NotNull
    @Embedded
    private ProjectDetails details;

    @OneToMany(mappedBy = "project")
    private List&lt;Member&gt; members = new ArrayList&lt;&gt;();

    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @Embeddable
    public static class ProjectDetails {

        @NotNull
        private LocalDateTime startTime;

        @NotNull
        private LocalDateTime endTime;
    }
}

Member.java

@Entity
public class Member {

    @Id
    @GeneratedValue
    private UUID id;

    @NotNull
    private String name;

    @NotNull
    private String surname;

    @NotNull
    private String role;

    @ManyToOne
    @JoinColumn(name = "project_id")
    private Project project;
}

在数据库中,我们应该有以下可用的表列表:

数据审计

要指示哪些业务数据应该接受审计机制,我们可以使用三种方式。

第一种是用@JaversSpringDataAuditable注释标记所选实体的存储库。添加此注释后,调用任何方法来修改此存储库上的数据将导致有关此操作的信息放置在其中一个 JaVers 表中。

在我们的应用程序中,我们只会将注释添加到Project类存储库中,如下所示:

    @Repository
    @JaversSpringDataAuditable
    public interface ProjectRepository extends JpaRepository&lt;Project, UUID&gt; {
    }

    @Repository
    public interface MemberRepository extends JpaRepository&lt;Member, UUID&gt; {
    }
第二种方法,允许我们调用数据的审计机制,是使用@JaversAuditable注释,我们可以在所选方法上方添加注释来修改数据。此类解决方案的示例如下所示:

服务层方法

    @JaversAuditable
    public void save(Project project) {
        //save project
}

存储库层方法

@Override
@JaversAuditable
S extends Project> S save(S entity);

我们可以保存审计数据的最后一种方法是在Javers类的对象上调用commit方法,我们可以使用依赖注入机制使用它。实际显示此解决方案的代码片段如下所示:

@RequiredArgsConstructor
public class ProjectService {

    private final Javers javers;

    public void commitProject() {
        Project project = new Project();//for simplicity ommited passing parameters
        javers.commit("author", project);
    }
}

作者提供程序配置

在实践中使用这些方法之前,我们仍然需要指定谁是记录的审计数据的作者。下面显示了用于确定此信息的基本配置示例。

 @Configuration
public class JaversAuthorConfiguration {

    @Bean
    public AuthorProvider provideJaversAuthor() {
        return new SimpleAuthorProvider();
    }

    private static class SimpleAuthorProvider implements AuthorProvider {
        @Override
        public String provide() {
            return "Freddie Mercury";
        }
    }
}

在实践中审计数据

为了在实践中验证所讨论的机制,调用了插入和修改数据方法,从而生成了INITIALUPDATE类型的审计数据。调用每个方法的结果如下所示:

添加新数据

插入新业务数据的方法:

    public void save() {
        Project.ProjectDetails projectDetails = Project.ProjectDetails.builder()
                .startTime(LocalDateTime.now())
                .endTime(LocalDateTime.now().plusDays(14))
                .build();

        Project project = Project.builder()
                .name("Project 1")
                .details(projectDetails)
                .build();

        Member member = Member.builder()
                .name("Brian")
                .surname("May")
                .role("guitarist")
                .project(project)
                .build();

        project.getMembers().add(member);

        projectRepository.saveAndFlush(project);
}

调用上述方法的结果是在jv_snapshot表中生成的以下条目:

值得注意的是,尽管仅在项目实体存储库上添加了@JaversSpringDataAuditable注释,但成员实体数据也已写入。

默认情况下,JaVers 包括属于审核实体的所有嵌套模型。我们可以通过在要从审计机制中省略的字段上添加@DiffIgnore注释来更改此行为。

在我们的例子中,这个注释的使用如下所示:

@DiffIgnore
    @Builder.Default
    @OneToMany(mappedBy = "project", cascade = CascadeType.ALL)
private List<Member> members = new ArrayLis<>();

修改现有数据

反过来,修改以前插入的数据的方法是:

   public void update(UUID uuid) {
        Project project = projectRepository.getById(uuid);
        project.setName("Live Aid");
        projectRepository.saveAndFlush(project);
}

但是,调用上述方法会导致生成其他条目,如下表所示:

下载审核数据

由于库提供的API,存储的审计数据可以从存储库中检索,称为JaVers查询语言(link:JQL (JaVers Query Language) examples — JaVers Documentation)。可用方法返回的数据可以以以下三种形式之一呈现:

  • 影子 – 包含域对象的历史数据,这些数据是从快照(快照)重新创建的
  • 更改 – 表示两个对象属性之间的差异
  • 快照 – 包含域对象的历史数据,表示为具有值的属性映射

以下是以所讨论的每种形式返回数据的代码片段。此外,在每个代码段下,还显示了调用该方法后返回的数据示例。

影子查询

查询:

 public String getShadow() {
        JqlQuery query = QueryBuilder.byClass(Project.class).build();
        List&lt;Shadow&lt;Object&gt;&gt; shadows = javers.findShadows(query);

        return javers.getJsonConverter().toJson(shadows);
}
响应:
  [
  {
    "commitMetadata": {
      "author": "Freddie Mercury",
      "properties": [],
      "commitDate": "2022-05-03T20:02:27.554",
      "commitDateInstant": "2022-05-03T18:02:27.554689400Z",
      "id": 2.00
    },
    "it": {
      "id": "37bfc44c-ab29-44b5-869c-b39b705bdfdf",
      "name": "Live Aid",
      "details": {
        "startTime": "2022-05-03T20:02:12.274037",
        "endTime": "2022-05-17T20:02:12.274037"
      }
    }
  },
  {
    "commitMetadata": {
      "author": "Freddie Mercury",
      "properties": [],
      "commitDate": "2022-05-03T20:02:12.343",
      "commitDateInstant": "2022-05-03T18:02:12.343208800Z",
      "id": 1.00
    },
    "it": {
      "id": "37bfc44c-ab29-44b5-869c-b39b705bdfdf",
      "name": "Project 1",
      "details": {
        "startTime": "2022-05-03T20:02:12.2740372",
        "endTime": "2022-05-17T20:02:12.2740372"
      }
    }
  }
]

如我们所见,返回的响应包含有关同一对象不同版本的信息。例如,返回的数据没有关于给定版本中哪个值已更改的专用信息。

更改查询

查询:

   public String getChanges() {
        JqlQuery query = QueryBuilder.byClass(Project.class).build();
        Changes changes = javers.findChanges(query);

        return javers.getJsonConverter().toJson(changes);
}
响应:
  [
  {
    "changeType": "ValueChange",
    "globalId": {
      "entity": "io.devapo.javerssample.entity.Project",
      "cdoId": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
    },
    "commitMetadata": {
      "author": "Freddie Mercury",
      "properties": [],
      "commitDate": "2022-05-03T20:02:27.554",
      "commitDateInstant": "2022-05-03T18:02:27.554689400Z",
      "id": 2.00
    },
    "property": "name",
    "propertyChangeType": "PROPERTY_VALUE_CHANGED",
    "left": "Project 1",
    "right": "Live Aid"
  },
  {
    "changeType": "NewObject",
    "globalId": {
      "entity": "io.devapo.javerssample.entity.Project",
      "cdoId": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
    },
    "commitMetadata": {
      "author": "Freddie Mercury",
      "properties": [],
      "commitDate": "2022-05-03T20:02:12.343",
      "commitDateInstant": "2022-05-03T18:02:12.343208800Z",
      "id": 1.00
    }
  },
  {
    "changeType": "InitialValueChange",
    "globalId": {
      "entity": "io.devapo.javerssample.entity.Project",
      "cdoId": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
    },
    "commitMetadata": {
      "author": "Freddie Mercury",
      "properties": [],
      "commitDate": "2022-05-03T20:02:12.343",
      "commitDateInstant": "2022-05-03T18:02:12.343208800Z",
      "id": 1.00
    },
    "property": "id",
    "propertyChangeType": "PROPERTY_VALUE_CHANGED",
    "left": null,
    "right": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
  },
  {
    "changeType": "InitialValueChange",
    "globalId": {
      "entity": "io.devapo.javerssample.entity.Project",
      "cdoId": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
    },
    "commitMetadata": {
      "author": "Freddie Mercury",
      "properties": [],
      "commitDate": "2022-05-03T20:02:12.343",
      "commitDateInstant": "2022-05-03T18:02:12.343208800Z",
      "id": 1.00
    },
    "property": "name",
    "propertyChangeType": "PROPERTY_VALUE_CHANGED",
    "left": null,
    "right": "Project 1"
  }
]

在上面的示例中,我们可以看到仅针对Project类中的字段返回更改信息,省略了嵌入的ProjectDetails类型的值。

生成的响应列出了有关对象创建和特定字段值分配的信息。因此,在以Changes 的形式调用数据查询时,值得通过设置适当的筛选器来缩小搜索区域。

快照查询

查询:

public String getSnapshot() {
        JqlQuery query = QueryBuilder.byClass(Project.class).build();
        List&lt;CdoSnapshot&gt; snapshots = javers.findSnapshots(query);

        return javers.getJsonConverter().toJson(snapshots);
}
响应:
 [
  {
    "commitMetadata": {
      "author": "Freddie Mercury",
      "properties": [],
      "commitDate": "2022-05-03T20:02:27.554",
      "commitDateInstant": "2022-05-03T18:02:27.554689400Z",
      "id": 2.00
    },
    "globalId": {
      "entity": "io.devapo.javerssample.entity.Project",
      "cdoId": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
    },
    "state": {
      "name": "Live Aid",
      "details": {
        "valueObject": "io.devapo.javerssample.entity.Project$ProjectDetails",
        "ownerId": {
          "entity": "io.devapo.javerssample.entity.Project",
          "cdoId": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
        },
        "fragment": "details"
      },
      "id": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
    },
    "changedProperties": [
      "name"
    ],
    "type": "UPDATE",
    "version": 2
  },
  {
    "commitMetadata": {
      "author": "Freddie Mercury",
      "properties": [],
      "commitDate": "2022-05-03T20:02:12.343",
      "commitDateInstant": "2022-05-03T18:02:12.343208800Z",
      "id": 1.00
    },
    "globalId": {
      "entity": "io.devapo.javerssample.entity.Project",
      "cdoId": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
    },
    "state": {
      "name": "Project 1",
      "details": {
        "valueObject": "io.devapo.javerssample.entity.Project$ProjectDetails",
        "ownerId": {
          "entity": "io.devapo.javerssample.entity.Project",
          "cdoId": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
        },
        "fragment": "details"
      },
      "id": "37bfc44c-ab29-44b5-869c-b39b705bdfdf"
    },
    "changedProperties": [
      "name",
      "details",
      "id"
    ],
    "type": "INITIAL",
    "version": 1
  }
]

在上面的示例中,我们可以看到Snapshot响应包含与Shodow响应类似的数据集,但包含一些其他元素,例如更改值的列表或审计数据类型(初始更新)。

总结

上面的文章介绍了JaVers工具,它允许您促进和部分自动化数据审计过程。在我们看来,此工具可以让您满足许多项目要求,同时实现它所需的少量工作。我们很高兴与该库分享我们的经验,并鼓励您尝试一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/45751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RT-thread lts-v3.1.x版本,GD32F450以太网,上电之后有一定概率ping不通问题处理。

先给结论 官方驱动没有按照GD32F4XX手册要求&#xff0c;等待ENET_DMA_CTL第20bit清0后再写 synopsys_emac.c 文件&#xff0c;void EMAC_FlushTransmitFIFO(struct rt_synopsys_eth * ETHERNET_MAC)函数&#xff0c;增加一句判断即可解决。 /*** Clears the ETHERNET transm…

Kotlin高仿微信-第4篇-主页-消息

Kotlin高仿微信-项目实践58篇详细讲解了各个功能点&#xff0c;包括&#xff1a;注册、登录、主页、单聊(文本、表情、语音、图片、小视频、视频通话、语音通话、红包、转账)、群聊、个人信息、朋友圈、支付服务、扫一扫、搜索好友、添加好友、开通VIP等众多功能。 Kotlin高仿…

Android Studio / IDEA 调试金手指:live template自动打印方法名以及所有变量

ctrl alt s 搜设置&#xff0c;template&#xff0c;结果是在 live template 区域设置代码模板的&#xff0c;不知这功能和直播有何关系&#xff0c;live stream&#xff1f; live template 就是自动完成一段代码。比如输入 fori&#xff0c;然后ctrl空格补全循环体&#xf…

Apache-DButils以及Druid(德鲁伊) 多表连接查询的解决方案:两种

Apache-DButils以及Druid(德鲁伊) 多表连接查询的问题 每博一文案 张爱玲说&#xff0c;于千万人之中&#xff0c;遇到你所要遇到的人&#xff0c;于千万年之中&#xff0c;时间的无涯的荒野里&#xff0c;没有 早一步&#xff0c;也没有晚一步&#xff0c;刚巧赶上了。 人生海…

iPhone开机密码什么时候会用到?忘记了怎么办?

iPhone的开机密码也是屏幕解锁密码&#xff0c;它的作用还是很重要的。一般用在&#xff1a; 解锁手机手机重启后解锁手机系统更新后第一次解锁手机手机连接电脑需要信任设备Face ID或指纹解锁失败三次后连接Apple Watch后第一次解锁手机 虽然我们现在经常使用其他的解锁方式&…

马斯克特斯拉内部邮件火了:痛恨开会,少说黑话

金磊 羿阁 发自 凹非寺量子位 | 公众号 QbitAI马斯克给员工的一封内部邮件火了。鼓励员工拒绝开会、公司规定不合理可以不遵守……俨然一个为员工着想的好老板。一开始人们还奇怪马斯克的画风怎么变这么快&#xff0c;后来才发现原来这是他6年前写的。对象也不是推特员工&#…

BCN点击试剂:1516551-46-4,BCN-succinimidylester,BCN NHS

●中文名&#xff1a;丙烷环辛炔-活性酯&#xff0c;BCN-琥珀酰亚胺酯 ●英文名&#xff1a;BCN-NHS&#xff0c; BCN-NHS 酯&#xff0c;BCN-活性酯&#xff0c;BCN-succinimidylester 【产品理化指标】&#xff1a; CAS号&#xff1a; 1516551-46-4 分子式&#xff1a;C15H17…

58 - 类模板的概念和意义

---- 整理自狄泰软件唐佐林老师课程 1. 思考 在C中是否能够将泛型的思想应用于类&#xff1f; 1.1 类模板 一些类主要用于存储和组织数据元素类中数据组织的方式和数据元素的具体类型无关 如&#xff1a;数组类、链表类、Stack类、Queue类&#xff0c;等 C中模板的思想应用于…

【LeetCode】No.103. Binary Tree Zigzag Level Order Traversal -- Java Version

题目链接&#xff1a;https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/ 1. 题目介绍&#xff08;Binary Tree Zigzag Level Order Traversal&#xff09; Given the root of a binary tree, return the zigzag level order traversal of its nodes’…

【网络编程】第二章 网络套接字(socket+UDP协议程序)

&#x1f3c6;个人主页&#xff1a;企鹅不叫的博客 ​ &#x1f308;专栏 C语言初阶和进阶C项目Leetcode刷题初阶数据结构与算法C初阶和进阶《深入理解计算机操作系统》《高质量C/C编程》Linux ⭐️ 博主码云gitee链接&#xff1a;代码仓库地址 ⚡若有帮助可以【关注点赞收藏】…

html实训大作业《基于HTML+CSS+JavaScript红色文化传媒网站(20页)》

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

群晖修改默认端口为80、443

写之前哔哔两句 我这个人是个有强迫症的人&#xff0c;本来群晖用的好好的&#xff0c;然后觉得为什么还要输入5000、5001端口呢&#xff1f; 然后我就尝试着去修改端口&#xff0c;想修改为40、443的时候&#xff0c;结果提示端口被保留&#xff0c;这我哪能忍&#xff0c;ss…

springboot整合canal

该篇博客是基于前两篇的基础上来实现的&#xff0c;如果没有看过可以看一下前面的步骤 使用docker搭建 MYSQL主从_极速小乌龟的博客-CSDN博客docker 上面搭建mysql主从服务器https://blog.csdn.net/qq_35771266/article/details/128101019?spm1001.2014.3001.5501 ShardingS…

Matlab optimtool优化阵列天线的幅相激励

摘要&#xff1a; 阵列天线的激励幅度和相位控制着其方向图形状。例如锥削分布的幅度可实现低副瓣、递变相位激励可改变波束指向&#xff0c;采用幅相综合控制则可实现平顶波束、余割平方等波束赋形。下面介绍利用Matlab optimtool优化阵列天线的幅相激励实现上述需求。 推文…

超市结算系统|Springboot+Vue通用超市结算收银系统

作者主页&#xff1a;编程千纸鹤 作者简介&#xff1a;Java、前端、Pythone开发多年&#xff0c;做过高程&#xff0c;项目经理&#xff0c;架构师 主要内容&#xff1a;Java项目开发、毕业设计开发、面试技术整理、最新技术分享 收藏点赞不迷路 关注作者有好处 项目编号&…

JavaScript基础语法(变量)

JavaScript基础语法&#xff08;变量&#xff09; 学习路线&#xff1a;JavaScript基础语法&#xff08;输出语句&#xff09;->JavaScript基础语法&#xff08;变量&#xff09;->JavaScript基础语法&#xff08;数据类型&#xff09;->JavaScript基础语法&#xff…

(一)DepthAI-python相关接口:OAK Device

消息快播&#xff1a;OpenCV众筹了一款ROS2机器人rae&#xff0c;开源、功能强、上手简单。来瞅瞅~ 编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查…

SuperMap iPortal 与独立代理服务的 session 共享通过redis配置实现

作者&#xff1a;yx 文章目录前言一、支持的Tomcat系列二、使用步骤1.将 /lib 中所有的 jar 拷贝到 tomcat/lib 目录2.给 tomcat 添加一个系统环境变量 "catalina.base"&#xff0c;变量取值为 tomcat 的根目录3、修改 redis 的相关配置4、在 【SuperMap iPortal / i…

Dreamweaver简单网页——HTML+CSS小米官网首页的设计与实现

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

数字化时代,中小企业如何落地数字化转型

大数据时代&#xff0c;各行各业的行业龙头和大型集团都已经开始了数据管理&#xff0c;让数据成为数据资产。但是在我国&#xff0c;中小企业的数量巨大&#xff0c;很多管理者忽视了这一点&#xff0c;今天我们就来聊一聊中小企业的数字化转型。 中小企业需要数字化转型 首…