Total Pageviews

2011/07/28

Handling JPA lifecycle event using listeners and callbacks

Problem
As we manipulate table, we need to insert values into user id(使用者代號), timestamp(西元年) and update date(民國年) as we do insert or update.
It's prone to forget to insert values to these columns.

Solution
Define setting value method and utilize JPA lifecyle to invoked automatically by JPA when these events occur.

JPA Lifecycle Events
Callback methods are user defined methods that are attached to entity lifecycle events and are invoked automatically by JPA when these events occur.

Internal callback methods should always return void and take no arguments. They can have any name and any access level (public, protected, package and private) but should not be static.

The annotation specifies when the callback method is invoked:
  • @PrePersist - before a new entity is persisted (added to the EntityManager).
  • @PostPersist - after storing a new entity in the database (during commit or flush).
  • @PostLoad - after an entity has been retrieved from the database.
  • @PreUpdate - when an entity is identified as modified by the EntityManager.
  • @PostUpdate - after updating an entity in the database (during commit or flush).
  • @PreRemove - when an entity is marked for removal in the EntityManager.
  • @PostRemove - after deleting an entity from the database (during commit or flush).

Implementation Restrictions
To avoid conflicts with the original database operation that fires the entity lifecycle event (which is still in progress) callback methods should not call EntityMan­ager or Query methods and should not access any other entity objects.

If a callback method throws an exception within an active transaction, the transaction is marked for rollback and no more callback methods are invoked for that operation.

How to do it
1. Define EntityListener class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package gov.fdc.nig.domain.listener;

import gov.fdc.common.util.DateUtil;
import gov.fdc.nig.util.NigUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Calendar;

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;

public class EntityListener {

 /**
  * Set date time and user id
  *
  * This method will be invoke before a new entity is persisted or
  * when an entity is identified as modified by the EntityManager
  *
  * @param obj Object
  * @throws IllegalArgumentException
  * @throws IllegalAccessException
  * @throws InvocationTargetException
  */
 @SuppressWarnings("unused")
 @PrePersist
 @PreUpdate
 private void setTimeAndUser(Object obj) throws IllegalArgumentException,
  IllegalAccessException, InvocationTargetException {
   Method methods[] = obj.getClass().getDeclaredMethods();
   String methodName = "";
   for (Method method: methods) {
    methodName = method.getName();
    if (methodName.matches("setUpdateDate")) {
     method.invoke(obj, DateUtil.getFormattedDate(NigUtils.getCurrentTimestamp(), "twyMMdd"));
    } else if (methodName.matches("setTimeStamp")) {
     method.invoke(obj, new Timestamp(Calendar.getInstance().getTime().getTime()));
    } else if (methodName.matches("setUserId")) {
     method.invoke(obj, NigUtils.getUser().getUserID());
    } else if (methodName.matches("setUpdateUserCd")) {
     method.invoke(obj, NigUtils.getUser().getUserID());
    }
   }
  }
}


2. add @EntityListeners({EntityListener.class}) annotation to each entity class

1
2
3
4
5
6
7
8
9
package gov.fdc.nig.domain;
@Entity
@EntityListeners({
 EntityListener.class
})
@Table(name = "NIGT036", schema = "AP_TAX")
public class Nigt036 extends DomainBase {
 //............
}

Therefore, we don't need to set these columns in controller class. And we won't forget to set values to theses columns again.

No comments: