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
}

класс входа:

@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
  • использование DescriptorEvent EclipsLink и соответствующих методов обратного вызова в прослушивателе сущностей. Это было самое многообещающее испытание. В preUpdate я мог бы добавить новый LogEntry к затронутому объекту. Добавленный LogEntry был даже правильно сохранен, но preUpdate будет вызываться любой операцией базы данных (select также приводит к вызову preUpdate), поэтому я не могу отличаться между измененным объектом и объектом без изменений. Предоставленные наборы изменений из события дескриптора, связанного запроса или unitOfWork в каждом случае имеют значение null. Сравнение текущего объекта и старого объекта (предоставленного событием дескриптора) слишком сложно, не так ли? С другой стороны, в preUpdateWithChanges я мог легко обнаружить измененный объект, но на данный момент, по-видимому, слишком поздно для добавления logentry. Этот экземпляр 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.

Envers является частью Hibernate с 3.6