Adding current user information to JPA entities

  2 posts   Feedicon  
Replies: 1 - Last Post: September 17, 2010 10:59
by: Michael Strasser
showing 1 - 2 of 2
 
Posted: September 16, 2010 23:55 by Michael Strasser

I would appreciate any comments from forum members on this issue.

Problem

I need to implement a database record auditing scheme in JPA to replace an existing one. The requirement is to include user and timestamp when each record is created and modified, into existing columns CREATE_USER, CREATE_DATE, MODIFY_USER and MODIFY_DATE. The current solution uses PL/SQL triggers and the current database user.

I am basing my design on a a blog entry I found that describes what I want to achieve: Reusing JPA models using embedded entities and composition.

Design

I will have PDOs and stateless EJBs to manage them. EJBs will be called with container-managed security.

I plan to create an embeddable type and an entity listener with @PrePersist and @PreUpdate annotations.

Who is the user?

The complication is getting the user information. (The blogged example I found relies on using UserContext.getCurrentUser() [source not shown] but I don’t have one of those.)

Pre-persist

This seems easy: the EJB sets the createUser field with SessionContext.getCallerPrincipal() before calling EntityManager.persist().

Pre-update

The EJB can specify a current user when it retrieves an entity but we only want to update the entity if one of the business fields changes (not an auditing field).

Use a transient field

This is my current proposed solution that covers both presist and update. The Auditable interface is:

public interface Auditable {
    public AuditInfo getAuditInfo();
}

The embeddable type is:

@Embeddable
public class AuditInfo {

    @Column(name="CREATE_USER")
    private String createUser;

    @Column(name="CREATE_DATE")
    @Temporal(TemporalType.TIMESTAMP)
    private Date createDate;

    @Column(name="MODIFY_USER")
    private String modifyUser;

    @Column(name="MODIFY_DATE")
    @Temporal(TemporalType.TIMESTAMP)
    private Date modifyDate;

    @Transient
    private String currentUser;

    public void setCurrentUser(String user) { currentUser = user; }

    public void prePersist() {
        createUser = currentUser;
        createDate = new Date();
    }

    public void preUpdate() {
        modifyUser = currentUser;
        modifyDate = new Date();
    }
}

An example entity includes:

@Entity
@EntityListeners(AuditListener.class)
public class Licence implements Auditable {

    @Embedded
    private AuditInfo auditInfo = new AuditInfo();

    public void setCurrentUser(String user) {
        auditInfo.setCurrentUser(user);
    }

    public void getAuditInfo() { return auditInfo; }

}

The entity listener class is:

public class AuditListener {

    @PrePersist
    public void auditCreation(Auditable auditable) {
        auditable.getAuditInfo().prePersist();
    }

    @PreUpdate
    public void auditUpdate(Auditable auditable) {
        auditable.getAuditInfo().preUpdate();
    }
}

The management EJB includes code like this:

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class LicenceManagerBean {

    @Resource
    SessionContext ctx;

    @PersistenceContext
    private EntityManager em;

    public Licence createLicence(/* ... */) {
        Licence licence = new Licence(/* ... */);
        licence.setCurrentUser(ctx.getCallerPrincipal().getName());
        em.persist(licence);
    }

    public Licence findByRef(String ref) {
        try {
            Licence licence = 
                (Licence)em.createNamedQuery(Licence.FIND_BY_REF)
                           .setParameter("ref", ref)
                           .getSingleResult();
        } catch (NoResultException nre) {
            // ...
        }
        licence.setCurrentUser(ctx.getCallerPrincipal().getName());
        return licence;
    }
}

Notes

  • It works this with Hibernate and Toplink Essentials.
  • It only makes sense to use callbacks on the embedding entity: callback annotations appear to be ignored in embedded classes; and entity listener annotations cannot be used on embedded classes.
  • I need to use an entity listener class because Toplink Essentials (at least) does not support annotations on fields and methods in the same class.
  • In a transaction-scoped persistence context created by a Service Façade called from the web container, am I right to assume there will only be one user for each transaction? I have not load-tested this yet (with multiple threads).
 
Posted: September 17, 2010 10:59 by Michael Strasser

A follow-up

I tried to automate things a bit by creating an EJB interceptor that would set the current user after an EJB method that returned one of my entities.

I had a Service Façade EJB (outer) that called an entity management bean with transaction attribute MANDATORY (inner). Methods on the inner bean were intercepted.

What I found was unexpected:

  • On persist, the pre-persist event always occurred before the inner EJB method returned and its interceptor acted, resulting in the CREATE_USER value never being set before transaction commit.
  • On update, the pre-update event always occurred after the inner EJB method returned. This was the behaviour I expected to see for both persist and update.

I found the same with both Hibernate and Toplink Essentials on OC4J.

Replies: 1 - Last Post: September 17, 2010 10:59
by: Michael Strasser
  • Mysql
  • Glassfish
  • Jruby
  • Rails
  • Nblogo
Terms of Use; Privacy Policy;
© 2010, Oracle Corporation and/or its affiliates
(revision 20120518.3c65429)
 
 
Close
loading
Please Confirm
Close