Как указать дату вступления в силу в исторической таблице, используя возможности аудита Spring Data JPA?

У меня есть таблица Product со столбцом имени, и я хотел бы сохранить историю изменений имени в таблице ProductHistory. (В таблице Product также есть несколько других столбцов. Мне не нужно отслеживать изменения данных для этих столбцов.)

введите описание изображения здесь

Мое приложение будет использовать ProductHistory таблицу, когда пользователь запрашивает запуск отчета за заданную дату. В отчете должны быть указаны названия продуктов на дату отчета.

Я использую Spring Boot 1.5.2 и включаю зависимость JPA от Spring Boot Starter Data. Я планирую использовать Hibernate Envers в рамках Spring Data JPA, чтобы помочь в достижении моей цели. Я прочитал этот документ но не знаю, как указать дату вступления в силу ProductHistory.

Я включил аудит JPA:

@Configuration
@EnableJpaRepositories("com.example.repository")
@EnableJpaAuditing
public class DatabaseConfig {
    //config items
}

Использование документа в качестве справки у меня должна быть сущность как:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @LastModifiedDate
    private Date effectiveDateUtc;

    private String name;

    //other fields corresponding to othercols
 }

Но effectiveDateUtc не является полем / столбцом Product. Как мне указать, что effectiveDateUtc является собственностью ProductHistory?

Обновить

Мне не удалось найти способ выполнить вышеуказанное, используя возможности аудита Spring Data JPA. Однако, используя ответ, опубликованный Naros / Hibernate envers, я смог достичь своих целей.

Помимо того, что опубликовал Naros, вот что еще я обнаружил, что мне нужно сделать, чтобы это работало с моим приложением Spring:

Добавьте необходимые jar-файлы в мою сборку gradle:

compile('org.hibernate:hibernate-envers:5.2.10.Final')
compile('org.hibernate:hibernate-core:5.2.10.Final')
compile('joda-time:joda-time:2.1')

Создал необходимую схему в БД.

Я также настроил тестовый пример интеграции, чтобы убедиться, что я могу выполнять запросы типа отчета по данным ревизии. Для этого мне нужно было получить диспетчер сущностей и передать его фабрике считывателя аудита:

@RunWith(SpringRunner.class)
@SpringBootTest(classes=ProductDataApplication.class)
public class ProductRepositoryTest {

    @PersistenceContext
    private EntityManager entityManager;

    @Test
    @Transactional
    public void testEnversQuerying() {
        AuditReader ar = AuditReaderFactory.get(entityManager);
        Product revisions = ar.find(Product.class, 1L, new Date());
        // assert code
    }
}

Как предположил Нарос, мне также нужно сохранить идентификатор пользователя, связанный с ревизией. Я сделал это следующим образом:

public class UserRevisionListener implements RevisionListener {

    @Override
    public void newRevision(Object revisionEntity) {
        UserRevision revision = (UserRevision) revisionEntity;

        if (SecurityContextHolder.getContext().getAuthentication() == null ||
                SecurityContextHolder.getContext().getAuthentication().getName() == null) {
            revision.setModifiedBy("system");
        } else {
            revision.setModifiedBy(SecurityContextHolder.getContext().getAuthentication().getName());
        }

    }
} 

person James    schedule 02.05.2017    source источник


Ответы (1)


Я понимаю, что вы, вероятно, ищете ответ, связанный с Spring; однако я могу по крайней мере предоставить вам подробную информацию о том, как это будет работать, если вы напрямую используете Hibernate Envers.

  1. Давайте настроим сущность:

    @Entity
    public class Product {
      @Id
      private Integer id;
      @Audited
      private String name;
      // other attributes
    }
    

    Как видите, мы в основном говорим, что просто хотим проверить название продукта. Любые другие атрибуты, которые вы сопоставили в сущности, будут просто игнорироваться, потому что класс не помечен @Audited, и мы специально нацелились только на свойство name.

  2. В отличие от модели ProductHistory, которую вы проиллюстрировали, Envers фактически создаст для вас две таблицы. Первой будет таблица Product_AUD, в которой мы будем хранить журнал аудита об изменениях, внесенных в вашу Product сущность.

    Таблица Product_AUD будет содержать столбцы, которые очень похожи на следующие:

    REV INT NOT NULL         <- Foreign Key to the Revision Entity Table (REVINFO)
    PRODUCT_ID INT NOT NULL  <- Your PK from your Product Table
    NAME VARCHAR(255)        <- The name column annotated with @Audited
    REVTYPE INT              <- Revision type (0=INSERT,1=UPDATE,2=DELETE)
    

    Следующая таблица, которая будет создана, будет REVINFO. По умолчанию эта таблица состоит всего из двух столбцов и будет выглядеть примерно так:

    REV INT NOT NULL         <- The revision number (PK)
    REVTSTMP BIGINT          <- The time in milliseconds
    

    Причина, по которой Envers строит модели таким образом, заключается в том, что нередко бывает, что несколько сущностей могут быть изменены в транзакции с момента, и вместо того, чтобы дублировать множество атрибутов, зависящих от версии, во всех таблицах аудита сущностей, мы консолидировали его нормализованным способом. и сохраните эти данные в таблице REVINFO.

Единственное, что вам может понадобиться, и что вы, кажется, обычно получаете напрямую с Spring Data, - это возможность захватывать пользователя и, возможно, другие атрибуты, которые относятся к среде на момент внесения изменений. Вам повезло, потому что Envers предлагает быстрый и безболезненный способ сделать это.

  1. Определите настраиваемую сущность ревизии. Это можно сделать, просто расширив одну из реализаций по умолчанию, которые мы вам предоставляем, или вы можете просто создать полностью функциональную сущность. Мое личное предпочтение и предложение - создать свою собственную полную реализацию. Эта сущность в основном расширяет REVINFO таблицу по умолчанию с помощью настраиваемых столбцов. Вот пример, который отражает настройку таблицы по умолчанию с одним дополнительным столбцом:

    @Entity
    @Table(name = "REVINFO")
    @RevisionEntity(MyRevisionEntityListener.class)
    public class MyRevisionEntity {
      @Id
      @RevisionNumber
      @Column(name = "REV", nullable = false, updatable = false)
      private Integer id;
      @RevisionTimestamp
      @Column(name = "REVTSTMP", nullable = false, updatable = false)
      private Long timestamp;
      @Column(name = "MODIFIED_BY", length = 100)
      private String modifiedBy;
      // getter/setters and perhaps other attributes
    }
    
  2. Определите прослушиватель сущности ревизии, который используется для заполнения ваших настраиваемых атрибутов в экземпляре сущности ревизии:

    class MyRevisionEntityListener implements RevisionListener {
      @Override
      public void newRevision(Object revisionEntity) {
      }
    }
    

    В этом слушателе вы хотите получить любую контекстную информацию, которую вы хотите ввести в сущность ревизии Envers, и установить ее в своем классе сущности ревизии.

    Один из способов получить информацию в этом слушателе - сохранить его в переменной ThreadLocal и получить доступ к этой переменной в обратном вызове слушателя. Если вы используете Spring Security в качестве примера, он уже сделал это за вас в своем SecurityContextHolder локальном контейнере потока. Если вы используете другие средства, вы можете просто взять их реализацию в качестве примера.

Как я уже упоминал ранее, это действительно не отвечает вашим потребностям в Spring Data напрямую, но показывает, как это происходит с Envers, если вы решите использовать его напрямую.

person Naros    schedule 03.05.2017
comment
Энверс потрясающий! И он хорошо работает с Spring Boot и Spring Data JPA, такими как репозитории и т. Д. Спасибо за ваш пост! Я провел дополнительные исследования, прочитав руководство пользователя. Затем настройте несколько интеграционных тестов, чтобы узнать и доказать, что он может удовлетворить потребности приложения. Он может делать это и многое другое! С нетерпением жду возможности использовать его в своем коде приложения. - person James; 06.05.2017