Question

I try to log any changes of my JPA entities. For this reason each entity inherits from an abstract entity class which has a list of LogEntry objects.

AbstractEntity class:

@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 class:

@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
}

My approach for this was to create a new LogEntry object and add it to the entity's LogEntry list before an entity got updated or persisted.

I tried the following solutions:

  • Using callback annotations (@PreUpdate, @PrePersist etc.) directly in the entity class or separated in an entity listener connected with AbstractEntity
  • Using EclipsLink's DescriptorEvent and the corresponding callback methods in an entity listener. This was the most promising trial. In preUpdate I could add a new LogEntry to the affected object. The added LogEntry was even correctly persisted, but preUpdate will be invoked by any database operation (select also leads to invocation of preUpdate), so I can't differ between a changed object and an object with no changes. The provided changesets from the descriptor event, the related query or the unitOfWork are in each case null. A comparison of the current object and the old object (provided by the descriptor event) is IMHO too complex, isn't it? On the other hand, in preUpdateWithChanges I could easily detect a changed entity, but at this point it is apparently too late for adding a logentry. The logentry won't be persisted.

Nearly each of these trials enables me to change attributes (like name, or validTo) of the affected entity. But no solution provides the opportunity to create a new LogEntry instance, or rather to persist this LogEntry instance. I also tried to get an instance of a session bean via jndi lookup to persists the LogEntry manually. The jndi lookup works, but calling create or update methods of the session bean has no effect.

My current entity listener looks like this:

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 is for various reasons no option.

EclipseLink Version is 2.3.2.

Was it helpful?

Solution

Persisting new object during events from the commit process can be difficult.

You could obtain the changes before the commit and persist you logs (get change set from the UnitOfWork).

See, http://wiki.eclipse.org/EclipseLink/FAQ/JPA#How_to_access_what_changed_in_an_object_or_transaction.3F

Otherwise, it is possible to directly insert and object from inside an event.

i.e.

event.getSession().insertObject(logEntry);

OTHER TIPS

Temporarily I solved this issue by comparing the current entity and the old entity in preUpdate. The comparison is done with EqualsBuilder from 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
}

You can try Tracking Changes Using History Policy

EclipseLink provides extended support for tracking all changes made to the database. The EclipseLink HistoryPolicy can be configured on a ClassDescriptor to store a mirror table of the original that will store the state of the object at any point in time. This can be used for auditing purposes, or to allow querying as of past points in time, or to allow restoring old data.

If you want simple entity auditing, then look no further than Hibernate's Envers module.

It works with JPA (and Spring-Data), and can store each changed version along with who changed it if you run say Spring Security.

Envers is part of Hibernate since 3.6

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top