0

0

Javers中处理一对多关系下ListChange对象的值获取策略

心靈之曲

心靈之曲

发布时间:2025-11-05 08:03:19

|

866人浏览过

|

来源于php中文网

原创

Javers中处理一对多关系下ListChange对象的值获取策略

本文深入探讨了在使用javers进行java springboot应用审计时,如何解决在一对多关系中,`listchange`对象仅提供子实体引用id而非实际对象值的问题。通过详细阐述`javers.findchanges`的局限性,并引入`javers.findshadows`方法,结合`withchangedpropertyin`和`tocommitid`等查询构建器,教程将指导开发者有效地检索历史版本中子实体的完整对象状态,从而实现精确的变更追踪和审计。

在现代企业级应用中,对数据变更进行审计是至关重要的一环。Javers作为一款强大的Java对象审计库,能够帮助开发者追踪实体对象的历史版本和变更。然而,在处理复杂的一对多关系时,开发者可能会遇到一个常见的问题:当子实体(集合中的元素)发生变更时,Javers的ListChange对象可能仅提供子实体的全局ID(Global Id)引用,而非其完整的对象值,这给理解具体变更内容带来了挑战。

审计场景与问题描述

考虑一个典型的父子实体关系,例如一个ParentEntity包含一个ChildEntity列表:

import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.List;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Setter
@NoArgsConstructor
class ParentEntity {

    @Id
    @GenericGenerator(name = "UUIDGenerator", strategy = "uuid2")
    @GeneratedValue(generator = "UUIDGenerator")
    @Column(name = "id", updatable = false, nullable = false)
    private UUID id;

    @Column(name = "customer_id")
    private String customerId;

    @OneToMany(mappedBy = "parentEntity", cascade = CascadeType.ALL, orphanRemoval = true)
    private List childEntity;
}
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.UUID;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
class ChildEntity {

    @Id
    @GenericGenerator(name = "UUIDGenerator", strategy = "uuid2")
    @GeneratedValue(generator = "UUIDGenerator")
    @Column(name = "id", updatable = false, nullable = false)
    protected UUID id;

    @ManyToOne(fetch = FetchType.LAZY)
    private ParentEntity parentEntity;

    // 其他属性,例如 String name; int value; 等
    private String name;
    private int value;
}

当ParentEntity中的childEntity列表发生变化,例如某个ChildEntity的属性被修改时,如果使用javers.findChanges方法查询变更,其结果可能包含一个ListChange对象。然而,这个ListChange对象在表示变更的left和right侧时,往往只包含ChildEntity的全局ID,而不是其完整的属性值。这意味着,仅凭ListChange,我们无法直接得知ChildEntity的具体哪个属性从什么值变为什么值。

例如,以下查询通常用于获取指定实例的变更:

import org.javers.core.Javers;
import org.javers.repository.jql.QueryBuilder;
import org.javers.core.diff.Change;
import java.util.List;
import java.util.UUID;

// 假设javers是Javers实例
// 假设parentId是ParentEntity的UUID
// 假设ParentEntity.class是实体类
List changes = javers.findChanges(
    QueryBuilder.byInstanceId(parentId, ParentEntity.class)
        .withSnapshotTypeUpdate()
        .build()
);

// 遍历changes时,如果遇到ListChange,其中的元素可能只有ID

这种情况下,我们得到的ListChange可能类似于:ListChange for childEntity, changed object: GlobalId(ChildEntity#id=UUID_A) -> GlobalId(ChildEntity#id=UUID_A),这并不能直接展示ChildEntity内部属性的变化。

解决方案:利用Javers的Shadows机制

为了获取子实体的完整对象值,我们需要利用Javers的“影子”(Shadows)机制。Shadows是Javers存储的历史对象快照,它们包含了实体在特定提交(Commit)时的完整状态。通过查询这些Shadows,我们可以重建出对象在不同时间点的完整视图。

Javers提供了javers.findShadows方法来检索这些历史快照。结合QueryBuilder,我们可以精确地定位到感兴趣的实体及其特定属性在某个提交时的状态。

核心方法与查询构建

要获取ChildEntity的实际值,我们可以采用以下查询模式:

import org.javers.core.Javers;
import org.javers.repository.jql.QueryBuilder;
import org.javers.core.commit.CommitId;
import org.javers.core.json.JsonConverter;
import org.javers.shadow.Shadow;
import java.util.List;
import java.util.UUID;

// 假设javers是Javers实例
// 假设parentId是ParentEntity的UUID
// 假设commitIdStr是导致ChildEntity变更的CommitId字符串,例如从findChanges中获取
// 假设ParentEntity.class是实体类

// 1. 根据CommitId和ParentEntity ID获取ParentEntity的Shadow
List> parentShadows = javers.findShadows(
    QueryBuilder.byInstanceId(parentId, ParentEntity.class)
        .withChangedPropertyIn("childEntity") // 可选,但有助于优化,聚焦于childEntity属性的变更
        .toCommitId(CommitId.parse(commitIdStr)) // 获取特定CommitId时的状态
        .build()
);

// 2. 从获取到的ParentEntity Shadow中提取ChildEntity的完整对象
if (!parentShadows.isEmpty()) {
    // 通常只会有一个匹配的ParentEntity Shadow
    Shadow parentShadow = parentShadows.get(0);
    ParentEntity parentAtCommit = (ParentEntity) parentShadow.get(); // 获取ParentEntity的实际对象

    // 现在parentAtCommit.getChildEntity()包含了在那个commitId时ChildEntity的完整列表和属性值
    List childEntitiesAtCommit = parentAtCommit.getChildEntity();

    // 遍历childEntitiesAtCommit,你可以获取每个ChildEntity的完整数据
    for (ChildEntity child : childEntitiesAtCommit) {
        System.out.println("ChildEntity ID: " + child.getId() + 
                           ", Name: " + child.getName() + 
                           ", Value: " + child.getValue());
    }
}

查询参数详解:

  • QueryBuilder.byInstanceId(Id, ParentEntity.class): 这是查询的基础,指定我们要查询的是哪个ParentEntity实例的历史快照。Id是ParentEntity的唯一标识符(通常是主键)。
  • withChangedPropertyIn("childEntity"): 这是一个重要的优化参数。它告诉Javers我们只对那些childEntity属性发生过变更的快照感兴趣。虽然不是强制性的,但在大型数据集中,它可以显著提高查询效率。
  • toCommitId(CommitId.parse(commitIdStr)): 这是获取特定时间点对象状态的关键。commitIdStr是从ListChange或其他Change对象中获取到的提交ID。通过指定这个ID,Javers会返回在该提交发生时ParentEntity及其关联ChildEntity的完整状态。

工作流程建议:

  1. 识别变更点: 首先,使用javers.findChanges(QueryBuilder.byInstanceId(...).build())来获取ParentEntity的所有变更。
  2. 定位ListChange: 在遍历Change列表时,识别出类型为ListChange的变更,并从中提取出相关的commitId以及涉及到的ChildEntity的ID(如果ListChange提供了)。
  3. 获取前后快照:
    • 使用上述javers.findShadows方法,传入识别出的ParentEntity的ID和commitId,获取发生变更时的ParentEntity快照。
    • 如果需要对比变更前后的状态,还需要找到commitId之前的最近一个提交ID,并再次调用findShadows获取变更前的ParentEntity快照。
  4. 比较并分析: 拿到两个不同commitId下的ParentEntity快照后,你可以比较它们各自childEntity列表中的ChildEntity对象,从而精确地分析出哪个ChildEntity的哪个属性发生了何种变化。

注意事项与最佳实践

  • 性能考量: findShadows会返回对象的完整快照,这可能比仅获取变更引用消耗更多的内存和处理时间。在设计审计查询时,应根据实际需求权衡。
  • CommitId管理: 准确地获取和管理CommitId是进行精确历史查询的关键。Javers的Change对象通常会包含其所属的CommitId。
  • 懒加载与Javers: 确保Javers配置正确处理JPA的懒加载(Lazy Loading)。通常,Javers在处理实体时会尝试加载所有属性,但这取决于你的Javers配置和JPA实体定义。如果ChildEntity的属性是懒加载的,在获取Shadow后访问这些属性时可能需要额外的处理。
  • Shadows的类型安全: findShadows返回List>,你需要将其强制转换为具体的实体类型(如ParentEntity)才能访问其属性。

总结

当Javers的ListChange对象在处理一对多关系时仅提供子实体的引用ID,而无法直接获取其完整值时,javers.findShadows方法是解决此问题的关键。通过结合QueryBuilder的byInstanceId、withChangedPropertyIn和toCommitId等方法,开发者可以精确地查询到特定提交时实体对象的完整快照。这种方法使得在复杂的审计场景中,能够深入分析并理解集合中子实体的具体变更内容,从而实现更精细、更准确的历史数据追踪。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

183

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

287

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

258

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

124

2025.08.07

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

469

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

13

2025.12.06

俄罗斯Yandex引擎入口
俄罗斯Yandex引擎入口

2026年俄罗斯Yandex搜索引擎最新入口汇总,涵盖免登录、多语言支持、无广告视频播放及本地化服务等核心功能。阅读专题下面的文章了解更多详细内容。

24

2026.01.28

包子漫画在线官方入口大全
包子漫画在线官方入口大全

本合集汇总了包子漫画2026最新官方在线观看入口,涵盖备用域名、正版无广告链接及多端适配地址,助你畅享12700+高清漫画资源。阅读专题下面的文章了解更多详细内容。

7

2026.01.28

ao3中文版官网地址大全
ao3中文版官网地址大全

AO3最新中文版官网入口合集,汇总2026年主站及国内优化镜像链接,支持简体中文界面、无广告阅读与多设备同步。阅读专题下面的文章了解更多详细内容。

28

2026.01.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.9万人学习

C# 教程
C# 教程

共94课时 | 7.8万人学习

Java 教程
Java 教程

共578课时 | 52.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号