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