EJB 3.0 Java Persistence API - "Refresh After Insert": Using EntityListeners to absorb the effects from Database Triggers americas cup win 2682133k1

EJB 3.0 Java Persistence API – "Refresh After Insert": Using EntityListeners to absorb the effects from Database Triggers

While preparing an article for JDJ as well as a presentation for
Oracle Open World next week, I finally got round to finding a proper
solution for a challenge I set myself during the JavaPolis 2005
University presentation by Mike Keith on EJB 3.0 Java Persistence API:
how do I get my entities to absorb any changes applied to the database
records during Insert (and Update) as a result of the action of
Database Triggers?

We at AMIS are Oracle database experts. We
know that database inside and out. Where some Java development shops
tend to shy away from the database apart from storing and retrieving
data in the plainest possible way, we are not afraid to use the
database for what it is good at. And we know that in many circumstances
our Java (Web) Application will not be the only application accessing
our database. Data Integrity for example must be implemented at the
database level to guarantee true data integrity. Along these same lines
lies the use of Database Triggers for several actions complementing
Insert, Update and Delete operations. An example of such a database
action is the derivation of (complex) default values and the
maintenance of auditing columns and denormalized values. Note that such
database triggers are not necessarily the consequence of our love for
the database, they may be a given in any situation you too might get
into.

The challenge we are facing in this situation: after
persisting a new entity, at some point in time the (EJB 3.0)
Persistency Provider will send the insert statement to the database;
typically this will happen when the flush() method is executed on the
EntityManager or the transaction on the EntityManager is committed –
though it may also happen at some earlier point in time if the provider
decides that is opportune. When the flush() is done or the transaction
is comitted, our – still managed – entity ideally is synchronized with
the database. However: if there are database triggers that fire on the
Insert operation to set additional (default) values or manipulate the
values that are inserted, we will have an aysnchronicity between database and entity even right after it was created in the first place. How do we have our entity refreshed?

How to refresh an entity in order to synchronize with the database?

Well, the JPA provides an EntityManager interface with a refresh method that takes an entity as input. We could use it. The question now is: when do we use it and who takes responsibility for calling it?

when: The call to refresh should take place no sooner than the database insert has taken place – and preferably not too long after it.

who: we would like to keep our entities free from intrusion by the framework or even the concept of persistence – at least apart from the necessary meta-data annotations. So we do not want to put the call to refresh inside the entity. What we need is an “entity watcher” who starts acting when an entity is in need of a refresh.

Enter the concept of the EntityListener (rather than an entity watcher). We can specify through the @EntityListeners annotation that one or more classes are interested in things happening to entities. Such classes are perfectly normal classes with one or more methods with the following signature:

public void anyNameYouLike( Entity entity)  
// or instead of the Entity class, one of the interfaces implemented by the entity 
// or one of the superclasses - even Object will do

To hook an entity listener to specific events, we have to add annotations in the entity listener to the methods that trigger on those events. For example to have a listener respond to the deletion of an entity’s associated record from the database, we can add the @PostRemove annotation to our ‘listener’ method.

In the example of ‘synchronizing the entity with database to absorb the effect of database triggers upon insert of the database record’ we need to use the @PostPersist annotation that specifies our listener to get called immediately after the actual insert into the database has taken place. Note: that can be when flush() is called on the entity manager, it can be when the transaction on the entity manager gets committed or whenever after the persist on the entity the persistence provider feels like sending the insert to the database.

The ‘refresh entity immediately after insert’ method could look like this:

    @PostPersist
    public void insertEntity(Object entity) {
        em.refresh(entity);
    }

A real example – well, a working one anyway…

This is another one of those Employee examples… But it demonstrates nicely how the refresh-after-insert might be set up. So please bear with me…

First of all – there was a database table called STAFF. It was created with the following DDL statements:

CREATE TABLE STAFF 
( EMPNO NUMBER(4,0) NOT NULL ENABLE, 
  ENAME VARCHAR2(10), 
  JOB VARCHAR2(9) DEFAULT 'WORKER', 
  MGR NUMBER(4,0), 
  HIREDATE DATE DEFAULT sysdate, 
  SAL NUMBER(12,2), 
  COMM NUMBER(7,2), 
  DEPTNO NUMBER(2,0), 
  CONSTRAINT STAFF_PK PRIMARY KEY (EMPNO)
) 

Then there were a sequence and a trigger:

CREATE SEQUENCE  STAFF_SEQ  
MINVALUE 1 MAXVALUE 9999 
INCREMENT BY 1 START WITH 1 
/

create or replace TRIGGER STAFF_INS BEFORE INSERT ON STAFF 
FOR EACH ROW 
begin
  if :new.empno is null or :new.empno =0 -- 0 gets assigned by EJB 3.0 JPA when no value is explicitly set on Entity
  then 
    select staff_seq.nextval
    into   :new.empno
    from dual
    ;
  end if;
  if :new.job is null
  then
      :new.job:='WORKER';
  end if;
  if :new.hiredate is null
  then
      :new.hiredate:= sysdate;
  end if;
end;
/

This trigger is fired whenever a record gets inserted into the STAFF table. When that happens, if EMPNO has no value yet, it will set the primary key (EMPNO) to the next value from the STAFF_SEQ sequence. If job is null, it will assign WORKER as default JOB value and if HIREDATE is empty, it will assign the current date as new hiredate.

Note: the Oracle database allows us to specify default values on columns. However, these will never play any part in records inserted by an EJB 30 JPA persistence provider (except for columns not mapped to the entities): the provider will insert values for all columns, inserting NULLs into columns for which the corresponding attributes were never set – causing no defaults to be applied. If you want to have default values applied you either have to set them in the entity itself (as part of the constructor or as initial values for the private fields) or you apply them through database triggers, as is done in this case. However, in the latter case you are perhaps assuming too much: the application may have had good reasons to set/leave the attribute value empty: who is the database to override that decision? Whatever is proper in your case, only you can decide.

Creating the Employee entity – mapped to the STAFF table

Our Java application needs to work with Employee objects and since we will make them persistent, mapped to the STAFF table, we will call them not just objects but entities.

The initial class definition for the Employee is as simple as can be(an):

package nl.amis.hrm.dao;

import java.io.Serializable;
import java.sql.Date;

public class Employee implements Serializable {
   
    private int empno;
    private String name;
    private String job;
    private float salalary;
    private Date hiredate;
   
    public Employee() {
    }

    public void setEmpno(int empno) {
        this.empno = empno;
    }

    public int getEmpno() {
        return empno;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public String getJob() {
        return job;
    }

    public void setSalalary(float salalary) {
        this.salalary = salalary;
    }

    public float getSalalary() {
        return salalary;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Date getHiredate() {
        return hiredate;
    }
}

We then add the necessary annotations to turn the bean into an entity; this will not change any of the accessor methods (getters and setters) so I only show the top part of the class definition:

package nl.amis.hrm.dao;

import java.io.Serializable;

import java.sql.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import nl.amis.hrm.EntityRefresher;


@Entity 
@EntityListeners({EntityRefresher.class})
@Table(name="Staff")
@NamedQuery(name = "Employee.findAll", query = "select o from Employee o")
public class Employee implements Serializable {
   
    @SequenceGenerator(sequenceName="Staff_Seq", name="StaffGen" , initialValue=1, allocationSize=1)
   
    @Id
    @GeneratedValue(generator="StaffGen", strategy=GenerationType.SEQUENCE)  
    private int empno;
    @Column(name="ename")
    private String name;
    private String job;


    @Column(name="sal")
    private float salalary;
    private Date hiredate;

Note the special annotation for EMPNO where we instruct our persistency provider to derive a value for EMPNO for newly persisted Employees from the database sequence STAFF_SEQ. You probably know about “configuration by exception”: the principle that allows us to only annotate what deviates from the default. Since the columns that the attributes hiredate and job are mapped to have the same name as the attributes, no additional @Column annotation is needed; they are still mapped – because of the fact that they belong to an @Entity and have names corresponding to columns in the @Table sitting underneath the entity. Since the name attribute is mapped to a column with a different name  – ENAME – we need to provide the @Column annotation, to specify the exception to the rule.

Right under the @Entity annotation, there is the @EntityListeners annotation. It contains a comma-separated (Array) list of class-names for all listener to events on this particular entity. In this case, there is just the one: EntityRefresher. So for any of the events that can be listened for – pre and post persist, update and remove as well as post load – the EntityRefresher class will be inspected for the corresponding @Pre… and @Post… annotations and when found get instantiated and called.

The EntityListener class: EntityRefresher

The Entity Listener class has one or more methods with a @PreEvent and or @PostEvent annotation. Note that a single method can be called for multiple events. It has no way though of finding out which event is taking place (or not that I know of). The method needs to take a single input parameter. The Entity Manager will pass the entity that triggered the listener for that input parameter. So the parameter must be of the entity type (or one of its superclasses) or be an interface that is implemented by the entity.

In this example, I have decided to create a generic entity listener class, one that can refresh any entity after it was inserted. The code is quite simple:

package nl.amis.hrm;

import javax.persistence.EntityManager;
import javax.persistence.PostPersist;
import nl.amis.hrm.dao.Employee;

public class EntityRefresher {
    public EntityRefresher() {
    }

    private static EntityManager em = null;

    public static void setEm(EntityManager entityManager) {
        em = entityManager;
    }

    @PostPersist
    public void insertEntity(Object entity) {
        em.refresh(entity);
        if (entity instanceof Employee) {
          System.out.println("Inserted staff " + ((Employee)entity).getName());
            System.out.println("Job = " + ((Employee)entity).getJob());
            System.out.println("Hiredate = " + ((Employee)entity).getHiredate());
        }
    }

}

Note that I have allowed a little bit of code for demonstration purposes. The EntityRefresher gets injected the EntityManager from somewhere at some point in time prior to the actual insert taking place. When the entities listened for are inserted, the insertEntity() method is called by the EntityManager. It uses the EntityManager handle em to refresh the entity that was just inserted.

Putting it to work…

In order to try this out, we need a persistence-unit in a persistence.xml file and some sort of service class that does the communication with the EntityManager. Well, the simplest piece of code for demonstrating the entity callbacks is something like this:

package nl.amis.hrm;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import javax.persistence.PostPersist;

import nl.amis.hrm.dao.Employee;

public class HrmService {
    static EntityManager em =null;
    public HrmService() {
    }
    
    
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hrm");
        em = emf.createEntityManager();
        EntityRefresher.setEm(em);
        
        Employee emp = new Employee();
        emp.setName("Tobias");
        emp.setSalalary(1000);
        em.getTransaction().begin();
        em.persist(emp);
        em.getTransaction().commit();
    }
}

When we run this code – with logging turned on – the output contains the following:

07:06:33.369--ServerSession(22528701)--Thread(Thread[main,5,main])--client acquired
07:06:33.409--ServerSession(22528701)--Connection(14069849)--Thread(Thread[main,5,main])
  --SELECT Staff_Seq.NEXTVAL FROM DUAL
07:06:33.729--UnitOfWork(5156931)--Thread(Thread[main,5,main])
  --begin unit of work commit
07:06:33.729--ClientSession(30254491)--Connection(33341602)--Thread(Thread[main,5,main])
  --begin transaction
07:06:33.739--ClientSession(30254491)--Connection(33341602)--Thread(Thread[main,5,main])
  --INSERT INTO Staff (EMPNO, JOB, sal, ename, HIREDATE) VALUES (?, ?, ?, ?, ?)
	bind => [21, null, 1000.0, Tobias, null]
07:06:34.430--ClientSession(30254491)--Connection(33341602)--Thread(Thread[main,5,main])
  --SELECT EMPNO, JOB, sal, ename, HIREDATE FROM Staff WHERE (EMPNO = ?)
	bind => [21]
Inserted staff Tobias
Job = WORKER
Hiredate = 2006-10-17
07:06:34.480--ClientSession(30254491)--Connection(33341602)--Thread(Thread[main,5,main])
  --commit transaction
07:06:34.480--UnitOfWork(5156931)--Thread(Thread[main,5,main])
  --end unit of work commit
07:06:34.490--UnitOfWork(5156931)--Thread(Thread[main,5,main])--resume unit of work

The sequence is used as soon as the persist is called for the new Employee entity. When the transaction is committed, the INSERT into the database takes place. Immediately after that, we see the refresh() method in action: select from staff where emp = 21. Next we see our own output: the new Employee is a WORKER – even though we never set the value for the Job attribute. Clearly this is the effect of the database triggers.

Note: this refresh from database relies on the value of the primary key. We cannot use this mechanism to have database triggers derive the value of the primary key from a sequence: this value must be known to the EntityManager before the persist/insert is performed.

Resources

Pro EJB 3: Java Persistence API By Mike Keith Merrick Schincariol ISBN: 1-59059-645-5

Blog by Raghu Kodali looking for the ‘refresh after insert trigger functionality in the JPA – EJB 3.0 – Refreshing entity with values populated from database triggers

Article on the presentation by Mike Keith (EJB 3.0 Specification Lead and Oracle TopLink expert) on EJB 3.0 JPA EJB 3.0 Reference Implementation with teeth – Open Source EJB 3.0 by Sun and Oracle (Report from JavaPolis)

JPA Annotations Reference for Oracle Toplink

4 Comments

  1. Jay December 4, 2006
  2. vatel October 23, 2006
  3. site admin October 21, 2006
  4. Duke Javastein October 21, 2006