我需要为项目中的所有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格式公开这些内容。
谢谢。
答案 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
中的每个条目都是基于以下配置的对象数组:
YourEntityClass
实例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对象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相同,只是它在对象数组中包含一个附加对象:
关于这种新方法的一个好主意不是使用反射或像我在resolveActions
中提到的某种类型的差异库,现在您已经明确地知道哪些属性被更改了,只需要获取那些属性即可。来自对象实例的特定值是非常琐碎的。
最后一种方法仍然是@Incubating
,因此被认为是实验性的。我可能会看到更改索引3,以便您返回一个Tuple<String,Object>
,其中包含可能带有该值的属性/字段名称,从而使用户可以更直接地使用它。