Hibernate Envers 修订之间的增量

Hibernate Envers Delta Between Revisions

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

现在我需要公开更改,用户可以在其中看到作为任何修订的一部分所做的更改。这是我想要增量输出的方式(为了便于阅读,我将其置于 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 的版本。如果您使用的是 Hibernate 5.2 及之前的版本,您的代码将需要进行一些额外的处理才能确定您想要的信息。

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

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

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

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

  • 索引 0 - YourEntityClass 该版本的实例
  • 索引 1 - 修订实体的具体实现(稍后详细介绍)。
  • 索引 2 - RevisionType 枚举值,ADDMODDEL。如果 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();

此查询return与上面提到的 5.2 的数组类型相同,只是它在对象数组中包含一个额外的对象:

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

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

最后一种方法仍然是 @Incubating,因此它被认为是实验性的。我可能会看到更改索引 3,这样您会返回一个 Tuple<String,Object>,其中包含可能带有值的 property/field 名称,从而使用户使用起来更加直接。