Отслеживание изменений в EclipseLink JPA

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

Класс AbstractEntity:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@EntityListeners(ChangeListener.class)
public abstract class AbstractEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Version
    private Long version;
    @Temporal(TemporalType.DATE)
    private Date validFrom;
    @Temporal(TemporalType.DATE)
    private Date validTo;
    private String name;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "abstractEntity")
    private List<LogEntry> logEntry = new ArrayList<LogEntry>();
    //getter and setter
}

Класс LogEntry:

@Entity
public class LogEntry extends AbstractEntity {

    @ManyToOne
    @JoinColumn
    protected AbstractEntity abstractEntity;
    @ManyToOne
    @JoinColumn
    protected Person person; // creator or updater
    @Column(updatable=false, insertable=false, columnDefinition="TIMESTAMP DEFAULT   CURRENT_TIMESTAMP")
    @Temporal(TemporalType.TIMESTAMP)
    protected Date changeDate;
    protected String comment;
    //getter and setter
}

Мой подход для этого состоял в том, чтобы создать новый объект LogEntry и добавить его в список LogEntry объекта, прежде чем объект будет обновлен или сохранен.

Я пробовал следующие решения:

  • Использование аннотаций обратного вызова (@PreUpdate, @PrePersist и т. Д.) Непосредственно в классе сущности или разделенных в слушателе сущности, связанном с AbstractEntity
  • Использование EclipsLink's DescriptorEvent и соответствующих методов обратного вызова в слушателе сущности. Это было самое многообещающее испытание. В preUpdate я мог добавить новый LogEntry для затронутого объекта. Добавленный LogEntry был даже правильно сохранен, но preUpdate будет вызываться любой операцией базы данных (выбор также приводит к вызову preUpdate), поэтому я не могу отличить измененный объект от объекта без изменений. Предоставленные наборы изменений из события дескриптора, связанного запроса или unitOfWork в каждом случае равны нулю. Сравнение текущего объекта и старого объекта (предоставленного событием дескриптора) ИМХО слишком сложно, не так ли? С другой стороны, в preUpdateWithChanges я мог легко обнаружить измененную сущность, но на данный момент уже слишком поздно для добавления logentry. Логентри не будет сохраняться.

Почти каждое из этих испытаний позволяет мне изменять атрибуты (например, имя или validTo) затрагиваемой сущности. Но ни одно решение не дает возможности создать новый экземпляр LogEntry, а точнее сохранить этот экземпляр LogEntry. Я также попытался получить экземпляр сессионного компонента через поиск jndi, чтобы вручную сохранить LogEntry. Поиск jndi работает, но вызов методов create или update сессионного компонента не имеет никакого эффекта.

Мой текущий слушатель сущности выглядит так:

public class ChangeListener extends DescriptorEventAdapter {

    @Override
    public void preUpdate(DescriptorEvent event) {
        AbstractEntity entity = (AbstractEntity) event.getObject();
        if (!(entity instanceof LogEntry)) {
            LogEntry logEntry = new LogEntry();
            logEntry.setPerson(getSessionController().getCurrentUser());
            logEntry.setAbstractEntity(entity);
            entity.getLogEntries().add(logEntry);
        }
    }
}

Hibernate Envers по разным причинам не вариант.

Версия EclipseLink 2.3.2.

4 ответа

Решение

Сохранение нового объекта во время событий процесса фиксации может быть затруднено.

Вы можете получить изменения до фиксации и сохранить ваши логи (получить набор изменений из UnitOfWork).

См. Http://wiki.eclipse.org/EclipseLink/FAQ/JPA#How_to_access_what_changed_in_an_object_or_transaction.3F.

В противном случае, можно напрямую вставить объект в событие.

т.е.

event.getSession().insertObject(logEntry);

Временно я решил эту проблему, сравнив текущий объект и старый объект в preUpdate. Сравнение сделано с EqualsBuilder из Apache Commons Lang.

public class ChangeAbstractEntityListener extends DescriptorEventAdapter {

    @Override
    public void preUpdate(DescriptorEvent event) {
        AbstractEntity originalEntity = (AbstractEntity) event.getOriginalObject();
        AbstractEntity entity = (AbstractEntity) event.getObject();

        if (!EqualsBuilder.reflectionEquals(originalEntity, entity, true)) {
            if (!(entity instanceof LogEntry)) {
                LogEntry logEntry = new LogEntry();
                logEntry.setPerson(getSessionController().getCurrentUser());
                logEntry.setAbstractEntity(entity);
                entity.getLogEntries().add(logEntry);
            }
        }
    }
    //jndi lookup to get the user object
}

Вы можете попробовать отслеживать изменения, используя историю политики

EclipseLink предоставляет расширенную поддержку для отслеживания всех изменений, внесенных в базу данных. EclipseLink HistoryPolicy может быть настроен на ClassDescriptor для хранения зеркальной таблицы оригинала, которая будет хранить состояние объекта в любой момент времени. Это может использоваться для целей аудита, или для того, чтобы разрешать запросы на прошлые моменты времени, или для разрешения восстановления старых данных.

Если вы хотите простой аудит сущностей, то смотрите не дальше, чем модуль Envers Hibernate.

Он работает с JPA (и Spring-Data) и может хранить каждую измененную версию вместе с тем, кто ее изменил, если вы запустите, скажем, Spring Security.

Энверс является частью Hibernate с 3.6

Другие вопросы по тегам