Как получить историю изменений, подобную JIRA, используя журнал аудита hibernate envers?

#jira #hibernate-envers

#jira #hibernate-envers

Вопрос:

Я пытаюсь показать историю изменений, подобную JIRA, в пользовательском интерфейсе. Я использую Spring Data JPA и настроил журнал аудита с помощью Envers (версия 5.3.7). Я могу получить список всех ревизий, используя AuditQuery , для конкретного объекта по его значению первичного ключа.

Есть ли простой способ вычислить «дельту» по ревизиям и определить свойства, которые были изменены? (Со старым и новым значением)

Я добавил @Audited(withModifiedFlag = true) аннотацию к своему классу сущностей. Он добавляет еще один столбец в <entity>_aud таблицу для каждого свойства, указывающий, было ли свойство изменено или нет. Я пытаюсь выяснить, как использовать эти дополнительные столбцы.

Комментарии:

1. С Envers 6.0 поставляется новый API, который позволяет вам использовать withModifiedFlag=true настройку атрибута и получать список ревизий для объекта, и он сообщит вам, какие поля были изменены в этой ревизии, поэтому вам не нужно использовать перехватчик или пользовательские таблицы; просто Envers.

Ответ №1:

Если вам нужно что-то вроде JIRA, вы должны создать его самостоятельно.

Я бы посоветовал вам использовать перехватчики Hibernate:

http://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#events

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

 public static class LoggingInterceptor extends EmptyInterceptor {
    @Override
    public boolean onFlushDirty(
        Object entity,
        Serializable id,
        Object[] currentState,
        Object[] previousState,
        String[] propertyNames,
        Type[] types) {
            LOGGER.debugv( "Entity {0}#{1} changed from {2} to {3}",
                entity.getClass().getSimpleName(),
                id,
                Arrays.toString( previousState ),
                Arrays.toString( currentState )
            );
            return super.onFlushDirty( entity, id, currentState,
                previousState, propertyNames, types
        );
    }
}
  

Комментарии:

1. Спасибо Саймону, что указал мне правильное направление. Вместо использования EmptyInterceptor я использовал org.hibernate.event.spi.PreUpdateEventListener. У меня это сработало. Я публикую свой код в этой теме.

Ответ №2:

Вот мой код

 import javax.annotation.PostConstruct;
import javax.persistence.EntityManagerFactory;

import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.PreUpdateEvent;
import org.hibernate.event.spi.PreUpdateEventListener;
import org.hibernate.internal.SessionFactoryImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AuditListener implements PreUpdateEventListener {   
    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @PostConstruct
    private void init() {
        SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
        EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);

        // You can also add listener for a specific entity-type instead of event-group
        // In my case I needed global event listener
        registry.getEventListenerGroup(EventType.PRE_UPDATE).appendListener(this);
    }

    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {
        String[] propertyNames = event.getPersister().getPropertyNames();
        Object[] oldValues = event.getOldState();
        Object[] newValues = event.getState();
        for (int index = 0; index < propertyNames.length; index  ) {
            String propertyName = propertyNames[index];
            Object oldValue = oldValues[index];
            Object newValue = newValues[index];

            // This is just sample code
            boolean changed = oldValue != newValue;
            if (changed) {
                System.out.println("Audit log -> Property: "   propertyName   ", Old value: "   oldValue   ", New value: "   newValue);

                // Actual code that persists audit log
                ...
                ...
            }
        }
        return false;
    }
}
  

Комментарии:

1. Как бы вы добавили прослушиватель для определенного типа объекта?