休眠会使修订之间产生变化量

时间:2019-01-23 16:10:56

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

我需要为项目中的所有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格式公开这些内容。

谢谢。

1 个答案:

答案 0 :(得分:3)

有两种方法可以完成您的要求,但这主要取决于您使用的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的第二个参数为false。如果该参数的值为true,则返回为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对象diff库之类的东西来确定两个实例之间的更改。如果您使用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<String,Object>,其中包含可能带有该值的属性/字段名称,从而使用户可以更直接地使用它。