首页 > Java > java教程 > 正文

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

心靈之曲
发布: 2025-11-05 08:03:19
原创
859人浏览过

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> 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<Change> 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内部属性的变化。

Cowriter
Cowriter

AI 作家,帮助加速和激发你的创意写作

Cowriter 107
查看详情 Cowriter

解决方案:利用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<Shadow<Object>> 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<Object> parentShadow = parentShadows.get(0);
    ParentEntity parentAtCommit = (ParentEntity) parentShadow.get(); // 获取ParentEntity的实际对象

    // 现在parentAtCommit.getChildEntity()包含了在那个commitId时ChildEntity的完整列表和属性值
    List<ChildEntity> 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<Shadow<Object>>,你需要将其强制转换为具体的实体类型(如ParentEntity)才能访问其属性。

总结

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

以上就是Javers中处理一对多关系下ListChange对象的值获取策略的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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