Hibernate Envers Delta между ревизиями - PullRequest
0 голосов
/ 23 января 2019

Мне нужно реализовать историю аудита для всех операций 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 голосов
/ 25 января 2019

Есть несколько способов выполнить то, что вы просите, но в основном это зависит от версии 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. Если бы значение этого аргумента было истинным, возвращение было бы List<YourEntityClass>.

Исходя из этого, каждая запись в List является массивом объектов на основе следующей конфигурации:

  • Индекс 0 - экземпляр YourEntityClass в этой ревизии
  • Индекс 1 - конкретная реализация сущности ревизии (подробнее об этом позже).
  • Index 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-объектов 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>, где оно содержит имя свойства / поля, потенциально со значением, что делает его гораздо более простым для использования пользователями.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...