【问题标题】:Hibernate Envers Delta Between Revisions修订之间的 Hibernate Envers Delta
【发布时间】:2019-01-23 16:10:56
【问题描述】:

我需要为我的项目中的所有 CRUD 操作实施审计历史记录。该项目使用 Spring JPA Data Rest。我四处寻找好的库来完成所需的任务,并遇到了这个Hibernate Envers,它看起来非常好并且易​​于实现。将其合并到我的项目中后,我能够记录所有 CRUD 操作的修订。

现在我需要公开更改,用户可以在其中看到所做的更改是任何修订的一部分。以下是我想要的 delta 输出(为了便于阅读,我将其放入 JSON 格式)。

[
  {
    "date": "9 may 2018, 6:06 pm",
    "user": "user.name (FName LName)",
    "actions": [
        {
            "field": "name",
            "oldValue": "Old Name very long",
            "newValue": "New Name also quite long."
        },
        {
            "field": "score",
            "oldValue": 2,
            "newValue": 4
        },
        {
            "field": "average_rating",
            "oldValue": "AA",
            "newValue": "A"
        }
    ]
},{
    "date": "10 may 2018, 5:06 pm",
    "user": "user.name (FName LName)",
    "actions": [
        {
            "field":"name",
            "oldValue": "Old Name",
            "newValue": "New Name"
        },
        {
            "field":"score",
            "oldValue": 1,
            "newValue": 6
        },
        {
            "field":"average_rating",
            "oldValue": "D",
            "newValue": "A+"
        },
        {
            "field":"rating",
            "oldValue": "A-",
            "newValue": "A"
        }
    ]
},{
    "date": "10 may 2018, 5:06 pm",
    "user": "user.name3 (FName3 LName3)",
    "actions": [
        {
            "field":"average_rating",
            "oldValue": "D",
            "newValue": "B"
        },
        {
            "field":"rating",
            "oldValue": "C",
            "newValue": "D"
        }
    ]
},{
    "date": "11 may 2018, 5:06 pm",
    "user": "user2.name2 (FName2 LName2)",
    "actions": [
        {
            "field":"score",
            "oldValue": 3,
            "newValue": 4
        },
        {
            "field":"average_rating",
            "oldValue": "C",
            "newValue": "B"
        }
    ]
},{
    "date": "9 apr 2018, 3:00 pm",
    "user": "user.name (FName LName)",
    "actions": [
        {
            "field":"name",
            "oldValue": "Old Name very long",
            "newValue": "New Name also quite long."
        },
        {
            "field":"score",
            "oldValue": 5,
            "newValue": 3
        },
        {
            "field":"average_rating",
            "oldValue": "AA",
            "newValue": "B"
        },
        {
            "field":"edf_score",
            "oldValue": 4,
            "newValue": 2
        },
        {
            "field":"edf_average_rating",
            "oldValue": "BBB+",
            "newValue": "BB"
        }
    ]
  }
]

我需要以 JSON-HAL 格式公开这些。

提前致谢。

【问题讨论】:

  • 您能否具体告诉我您正在使用哪个版本的 Hibernate 和 Envers,因为这会影响哪些 API 最适合您所需的输出?
  • 嗨 @Naros,Hibernate 和 Envers 的 5.2.17.Final

标签: hibernate spring-data-jpa hibernate-envers changeset


【解决方案1】:

有几种方法可以完成您的要求,但这主要取决于您使用的 Hibernate 和 Envers 版本。如果您使用的是 Hibernate 5.2 及之前的版本,您的代码将不得不进行一些额外的处理以确定您想要的信息。

我假设你有你感兴趣的实体的主键。

List results = AuditReaderFactory.get( session ).createQuery()
  .forRevisionsOfEntity( YourEntityClass.class, false, true )
  .add( AuditEntity.id().eq( entityId ) )
  .addOrder( AuditEntity.revisionNumber().asc() )
  .getResultList();

这个查询实际上返回一个List<Object[]>,因为forRevisionsOfEntity 的第二个参数是假的。如果此参数的值为 true,则返回值为 List<YourEntityClass>。

List 中的每个条目都是基于以下配置的对象数组:

  • 索引 0 - 该修订版中的 YourEntityClass 实例
  • 索引 1 - 修订实体的具体实现(稍后会详细介绍)。
  • 索引 2 - RevisionType 枚举值,ADD、MOD 或 DEL。如果forRevisionsOfEntity 的第三个参数为假,则永远不会有任何DEL 类型。

此时逻辑变成这样:

YourEntityClass previousInstance = null;
for ( int i = 0; i < results.size(); ++i ) {
  Object[] row = (Object[]) results.get( i );
  if ( previousInstance == null ) {
    // this is the first revision, consider nothing changed here
    // so store a reference to it for the next row.
    previousInstance = row[0];
  }
  else {
    final YourRevisionEntity revEntity = (YourRevisionEntity) row[1];
    final String userName = revEntity.getUserName();
    final long revisionTimestamp = revEntity.getTimestamp();

    final YourEntityClass currentInstance = (YourEntityClass) row[0];
    List<Action> actions = resolveActions( previousInstance, currentInstance );
    // build your things

    previousInstance = currentInstance;
  }
}

这里的主要内容是,在您的 resolveActions 方法中,您基本上使用反射或一些 java 对象差异库之类的东西来确定两个实例之间的变化。如果您使用withModifiedFlag 的想法,您可以为每个属性运行一个查询,但是如果所讨论的实体类型有很多列或者您往往有很多修订,这可能会对您的系统造成负担。

如果您使用的是 Hibernate 5.3,我们添加了一种方便的方法,可以稍微简化此过程,但它也依赖于 withModifiedFlag 概念。在这种特殊情况下,您最初会运行原始查询的稍微不同的修改版本

List results = AuditReaderFactory.get( session ).createQuery()
  .forRevisionsOfEntityWithChanges( YourEntityClass.class, false, true )
  .add( AuditEntity.id().eq( entityId ) )
  .addOrder( AuditEntity.revisionNumber().asc() )
  .getResultList();

此查询返回与上述 5.2 相同类型的数组,但它在对象数组中包含一个附加对象:

  • 索引 3 - 作为已更改属性的字符串集合。

关于这种新方法的好主意不是像我在resolveActions 中提到的那样使用反射或某种类型的差异库,现在您已经知道具体哪些属性被更改了,只需获取那些特定的值来自超级简单的对象实例。

最后一种方法仍然是@Incubating,因此它被认为是实验性的。我可能会看到更改索引 3,以便您返回 Tuple&lt;String,Object&gt;,其中包含可能带有值的属性/字段名称,从而使用户使用起来更加直接。

【讨论】:

  • 嗨@Naros,感谢您的回复。我正在做你刚才描述的事情(根据版本 5.2.17)。我正在使用 java-object-diff 库来比较这两个对象。你能告诉我一件事,我可以将 envers 升级到最新的 5.4.1.Final 但保持休眠到 5.2.17 并获取已更改/添加/删除的属性集合吗?大概属性列表将包括所有属性,无论它们是否已在修订之间添加/更新/删除。
  • 不幸的是你不能。由于hibernate-envers 和hibernate-core 是串联构建的,因此使用相同版本部署它们至关重要,以避免两个工件之间的兼容性问题。
  • 好的,谢谢。如果我需要检索实体的特定修订的修订历史。我的代码在正确的下面吗? AuditQuery auditQuery = AuditReaderFactory.get(entityManager).createQuery() .forEntitiesAtRevision(entityClass, revisionId) .add(AuditEntity.id().eq(financialGroupId)); @SuppressWarnings("unchecked") List resultList = auditQuery.getResultList(); if (resultList.size() != 1) { throw new IllegalStateException("Entity at Revision not found"); } 返回结果列表.get(0); }
  • 嗨@Naros,谢谢。如果我需要检索实体的特定修订的修订历史。我的代码在正确的下面吗? AuditQuery auditQuery = AuditReaderFactory.get(entityManager).createQuery() .forEntitiesAtRevision(entityClass, revisionId) .add(AuditEntity.id().eq(financialGroupId)); @SuppressWarnings("unchecked") List&lt;T&gt; resultList = auditQuery.getResultList(); if (resultList.size() != 1) { throw new IllegalStateException("Entity at Revision not found"); } return resultList.get(0); }
  • 看起来不错,是的。请注意,如果该版本恰好是描述删除的版本,则该方法将不返回任何结果,因为它被定义为仅返回插入和更新。
猜你喜欢
  • 2019-05-30
  • 2014-03-02
  • 2012-10-25
  • 1970-01-01
  • 1970-01-01
  • 2013-05-24
  • 1970-01-01
  • 2011-07-03
  • 1970-01-01
相关资源
最近更新 更多