JPA Entity Listeners And Callback Methods

Before JPA 2.1 the concept of Entity Listeners (Injectable EntityListeners) wasn’t defined until JPA 2.1 released. The entity listener concept allows the programmer to designate a methods as a lifecycle callback methods to receive notification of entity lifecycle events. A lifecycle callback methods can be defined on an entity class, a mapped superclass, or an entity listener class associated with an entity or mapped superclass.

Default entity listeners are entity listeners whose callback methods has been called by all entities that are defined in a certain persistence context (unit).  The lifecycle callback methods and entity listeners classes can be defined by using metadata annotations or by an XML descriptor.

Entity Listener (Default Listener) Using Entity Listener Class

If you’ve looked before for the introduction examples for eclipselink (See EclipseLink Tutorial), it’s time to listening about events that thrown by lifecycle of the entities. Let’s look at the first entity listener that would be listening for each defined entities in the persistence.xml.

PersistenceContextListener.java

package net.javabeat.eclipselink.data;

import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;

public class PersistenceContextListener {
 @PrePersist
 public void prePersist(Object object) {
 System.out.println("PersistenceContextListener :: Method PrePersist Invoked Upon Entity :: " + object);
 }

 @PostPersist
 public void postPersist(Object object){
 System.out.println("PersistenceContextListener :: Method PostPersist Invoked Upon Entity :: " + object);
 }

 @PreRemove
 public void PreRemove(Object object){
 System.out.println("PersistenceContextListener :: Method PreRemove Invoked Upon Entity :: " + object);
 }

 @PostRemove
 public void PostRemove(Object object){
 System.out.println("PersistenceContextListener :: Method PostRemove Invoked Upon Entity :: " + object);
 }

 @PreUpdate
 public void PreUpdate(Object object){
 System.out.println("PersistenceContextListener :: Method PreUpdate Invoked Upon Entity :: " + object);
 }

 @PostUpdate
 public void PostUpdate(Object object){
 System.out.println("PersistenceContextListener :: Method PostUpdate Invoked Upon Entity :: " + object);
 }

}
  • Entity lifecycle callback methods can be defined on an entity listener class.
  • Lifecycle callback methods are annotated with annotation that specifying the callback events for which they are invoked.
  • Callback methods defined on an entity listener class should have the following signatures: void <METHOD> (Object), the object argument is the entity class for which the callback method is invoked.
  • The callback methods can have public, private, protected or package level access, but must not be static or final.
  • The following annotations designates lifecycle event callback methods of the corresponding types (entities).
  1. PrePersist: This callback method is invoked for a given entity before the respective Entity Manager executes persist operation for that entity.
  2. PostPersist: This callback method is invoked for a given entity after the respective Entity Manager executes persist operation for that entity.
  3. PreRemove: This callback method is invoked for a given entity before the respective Entity Manager executes remove operation for that entity. 
  4. PostRemove: This callback method is invoked for a given entity after the respective Entity Manager executes remove operation for that entity.
  5. PreUpdate: This callback method is invoked for a given entity before the respective Entity Manager executes merge operation for that entity.
  6. PostUpdate: This callback method is invoked for a given entity after the respective Entity Manager executes merge operation for that entity.
  7. PostLoad: The PostLoad method for an entity is invoked after the entity has been loaded into the current persistence context from the database or after the refresh operation has been applied to it.

The Entity & Mapped SuperClass With Listener

After we’ve seen a separate entity listener, let’s see a listener that could be defined at the entity itself.

Address.java

package net.javabeat.eclipselink.data;

import javax.persistence.CascadeType;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.OneToOne;
import javax.persistence.PrePersist;

@Entity(name="address")
public class Address {
 @EmbeddedId
 private AddressPK addressId;
 private String addressCountry;
 private String addressCity;

 @OneToOne(cascade=CascadeType.ALL,mappedBy="address")
 private Employee employee;

 public Employee getEmployee() {
 return employee;
 }

 public void setEmployee(Employee employee) {
 this.employee = employee;
 }

public String getAddressCountry() {
 return addressCountry;
 }
 public void setAddressCountry(String addressCountry) {
 this.addressCountry = addressCountry;
 }
 public String getAddressCity() {
 return addressCity;
 }
 public void setAddressCity(String addressCity) {
 this.addressCity = addressCity;
 }

public AddressPK getAddressId() {
 return addressId;
 }

public void setAddressId(AddressPK addressId) {
 this.addressId = addressId;
 }

 @PrePersist
 public void prePersist(){
 System.out.println("Address Entity :: Method PrePersist Invoked Upon Entity "+this);
 }

}

License.java


package net.javabeat.eclipselink.data;

import javax.persistence.CascadeType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.PostRemove;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.TableGenerator;

@MappedSuperclass
public abstract class License {

@Id
 @GeneratedValue(generator="LICENSE_SEQ",strategy=GenerationType.SEQUENCE)
 @TableGenerator(name="LICENSE_SEQ",pkColumnName="SEQ_NAME",valueColumnName="SEQ_NUMBER",pkColumnValue="LICENSE_ID",allocationSize=1,table="sequences")
 protected int licenseId;

@ManyToOne(cascade = CascadeType.ALL)
 @JoinColumn(name = "employeeId")
 private Employee employee;

public Employee getEmployee() {
 return employee;
 }

public void setEmployee(Employee employee) {
 this.employee = employee;
 }

public int getLicenseId() {
 return licenseId;
 }

public void setLicenseId(int licenseId) {
 this.licenseId = licenseId;
 }

 @PrePersist
 public void prePersist(){
 System.out.println("License Entity :: Method PrePersist Invoked Upon Entity "+this);
 }

 @PreRemove
 public void peRemove(){
 System.out.println("License Entity :: Method PreRemove Invoked Upon Entity "+this);
 }

 @PostRemove
 public void postRemove(){
 System.out.println("License Entity :: Method PostRemove Invoked Upon Entity "+this);
 }
}

  • Entity lifecycle callback methods can be defined on an entity class.
  • Lifecycle callback methods are annotated with annotation that specifying the callback events for which they are invoked within the defined entity.
  • Callback methods defined on an entity listener class should have the following signatures: void <METHOD> ().
  • The callback methods can have public, private, protected or package level access, but must not be static or final.
  • The annotations that designate for achieving the listening for the entity lifecycle events are the same for those defined in the entity listener class that defined above.
  • The definition of the lifecycle events callback methods are the same as defined in the mapped superclass as license mapped superclass depicted.

Persistence Configuration

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
 <persistence-unit name="EclipseLink-JPA-Installation" transaction-type="RESOURCE_LOCAL">
 <class>net.javabeat.eclipselink.data.Employee</class>
 <class>net.javabeat.eclipselink.data.Address</class>
 <class>net.javabeat.eclipselink.data.AddressPK</class>
 <class>net.javabeat.eclipselink.data.License</class>
 <class>net.javabeat.eclipselink.data.DriverLicense</class>
 <properties>
 <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/JavaBeat"/>
 <property name="javax.persistence.jdbc.user" value="root"/>
 <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
 <property name="javax.persistence.jdbc.password" value="root"/>
 <property name="eclipselink.logging.level" value="OFF"/>
 </properties>
 </persistence-unit>
</persistence>

Executable Application


package net.javabeat.eclipselink;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

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

import net.javabeat.eclipselink.data.Address;
import net.javabeat.eclipselink.data.AddressPK;
import net.javabeat.eclipselink.data.ContractDeveloper;
import net.javabeat.eclipselink.data.Developer;
import net.javabeat.eclipselink.data.DriverLicense;
import net.javabeat.eclipselink.data.Employee;
import net.javabeat.eclipselink.data.EmployeePeriod;
import net.javabeat.eclipselink.data.FreelanceDeveloper;
import net.javabeat.eclipselink.data.GlobalProject;
import net.javabeat.eclipselink.data.ICDLComputerLicense;
import net.javabeat.eclipselink.data.License;
import net.javabeat.eclipselink.data.LocalProject;
import net.javabeat.eclipselink.data.Phone;
import net.javabeat.eclipselink.data.PhonePK;
import net.javabeat.eclipselink.data.Project;

public class JPAImpl {
 static EntityManagerFactory factory = null;
 static EntityManager em = null;
 static {
 factory = Persistence.createEntityManagerFactory("EclipseLink-JPA-Installation");
 em = factory.createEntityManager();
 }

 public static void main(String [] args){
 // Begin a Transaction for creation
 em.getTransaction().begin();
 // Find the Employee
 Employee employee = createEmployee();
 // Find the Employee
 License license = createDriverLicense(employee);
 // Commit
 em.getTransaction().commit();

 // Begin a Transaction for Update an Employee
 em.getTransaction().begin();
 // Find the Employee
 employee.setEmployeeName("Edward Halshmit");
 em.merge(employee);
 // Commit
 em.getTransaction().commit();

 // Begin a Transaction for removing an employee and driver license
 em.getTransaction().begin();
 // Remove License
 em.remove(license);
 // Find the Employee
 em.remove(employee);
 // Commit
 em.getTransaction().commit();

 }

 public static Employee createEmployee(){
 // Create an address entity
 Address address = new Address();
 AddressPK addressPk = new AddressPK();
 addressPk.setAddressCountryId(1);
 addressPk.setAddressId(1);
 address.setAddressId(addressPk);
 // Address Embeddable class (Type) instantiation
 address.setAddressCountry("United Kingdom");
 address.setAddressCity("London");
 // Create an employee entity
 Employee employee = new Employee();
 employee.setEmployeeId(1);
 employee.setEmployeeName("John Smith");

 // Create an Employee Period Instance
 EmployeePeriod period = new EmployeePeriod();
 period.setStartDate(new Date());
 period.setEndDate(new Date());

 employee.setEmployeePeriod(period);

 // Associate the address with the employee
 employee.setAddress(address);
 // Create a Phone entity
 Phone firstPhone = new Phone();
 // PhoneId and PhoneCountryKeyId is now the primary key for the phone entity
 firstPhone.setPhoneId(1);
 firstPhone.setPhoneCountryKeyId("+441");
 firstPhone.setPhoneNumber("4050 615");
 firstPhone.setEmployee(employee);
 // Create a list of phone
 List phones = new ArrayList();
 phones.add(firstPhone);

 // Create a list of projects
 List projects = new ArrayList();

 // Set the project into employee
 employee.setProjects(projects);
 // Set the phones into your employee
 employee.setPhones(phones);

 // Persist the employee

 em.persist(employee);

 // return the persisted employee
 return employee;
 }

public static License createDriverLicense(Employee employee){
 DriverLicense license = new DriverLicense(); // Create a driver license
 license.setDriverLicenseName("All Vehicles License"); // Set License Name
 license.setDriverLicenseIssueDate(new Date()); // Anonymous date
 license.setDriverLicenseExpiryDate(new Date()); // Anonymous date
 license.setEmployee(employee);
 em.persist(license);
 return license;
 }

}

The Rules Applied on the Lifecycle Callback Methods

The following rules apply to lifecycle callback methods:

  • Lifecycle callback methods may throw unchecked/runtime exceptions. A runtime exception thrown by a callback method that executes within a transaction causes that transaction to be marked for rollback if the persistence context is joined to the transaction.
  • Lifecycle callbacks can invoke JNDI, JDBC, JMS and enterprise beans.
  • In general, the lifecycle method of a portable application should not invoke EntityManager or query oprtations, access other instancesm or modify relationships within the same persistence context. A lifecycle callback methods may modify the non-relationship state of the entity on which it is invoked.

Precedence of Listeners Execution

The precedence of listeners execution are arranged as following:

  • Default Listener will be executed firstly, default listener apply to all entities in the persistence unit, so  if you’ve defined multiple listeners within @EntityListener as you’ve seen in the Employee entity, then the callback methods are invoked in the order as listener specified Unless explicitly excluded by means of the ExecludedDefaultListeners annotation.
  • The callback methods that are defined within the entity or mapped superclasses themselves should be executed after default listener and in the same order.
  • If multiple classes in an inheritance hierarchy (entity classes or mapped superclasses) define entity listeners, the listeners defined in the superclasses are invoked before the listeners defined for its sub-classes.

The Result of Executable Application


PersistenceContextListener :: Method PrePersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@d5d4de6
Address Entity :: Method PrePersist Invoked Upon Entity net.javabeat.eclipselink.data.Address@6e92b1a1
License Entity :: Method PrePersist Invoked Upon Entity net.javabeat.eclipselink.data.DriverLicense@47f08ed8
PersistenceContextListener :: Method PostPersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@d5d4de6
PersistenceContextListener :: Method PreUpdate Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@d5d4de6
PersistenceContextListener :: Method PostUpdate Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@d5d4de6
License Entity :: Method PreRemove Invoked Upon Entity net.javabeat.eclipselink.data.DriverLicense@47f08ed8
PersistenceContextListener :: Method PreRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@d5d4de6
License Entity :: Method PostRemove Invoked Upon Entity net.javabeat.eclipselink.data.DriverLicense@47f08ed8
PersistenceContextListener :: Method PostRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@d5d4de6

What if we’ve changed the License and Address entities by allowing them defining an @EntityListener at their declaration to reference the PersistenceContextListener as you’ve seen at the Employee entity.

The Result of Executable Application After last changed


PersistenceContextListener :: Method PrePersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@343aff84
PersistenceContextListener :: Method PrePersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Address@301db5ec
Address Entity :: Method PrePersist Invoked Upon Entity net.javabeat.eclipselink.data.Address@301db5ec
PersistenceContextListener :: Method PrePersist Invoked Upon Entity :: net.javabeat.eclipselink.data.DriverLicense@77546dbc
License Entity :: Method PrePersist Invoked Upon Entity net.javabeat.eclipselink.data.DriverLicense@77546dbc
PersistenceContextListener :: Method PostPersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Address@301db5ec
PersistenceContextListener :: Method PostPersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@343aff84
PersistenceContextListener :: Method PostPersist Invoked Upon Entity :: net.javabeat.eclipselink.data.DriverLicense@77546dbc
PersistenceContextListener :: Method PreUpdate Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@343aff84
PersistenceContextListener :: Method PostUpdate Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@343aff84
PersistenceContextListener :: Method PreRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.DriverLicense@77546dbc
License Entity :: Method PreRemove Invoked Upon Entity net.javabeat.eclipselink.data.DriverLicense@77546dbc
PersistenceContextListener :: Method PreRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@343aff84
PersistenceContextListener :: Method PreRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Address@301db5ec
PersistenceContextListener :: Method PostRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.DriverLicense@77546dbc
License Entity :: Method PostRemove Invoked Upon Entity net.javabeat.eclipselink.data.DriverLicense@77546dbc
PersistenceContextListener :: Method PostRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@343aff84
PersistenceContextListener :: Method PostRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Address@301db5ec

Inheritance of Callback Lifecycle Methods

The inheritance of callback lifecycle methods is applicable, so a lifecycle callback method for the same lifecycle event is also specified on the entity class or one or more of its entity or mapped superclasses, the callback methods on the entity class or superclasses are invoked first.

The sub-classes are permitted to override an inherited callback method of the same callback type (i.e. override the same method of superclass in the sub-class using the same annotation type), at this case the overridden method isn’t invoked. Let’s see the DriverLicense that override the prePersist method annotated with the same callback type (@PrePersist)

DriverLicense.java


package net.javabeat.eclipselink.data;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name="driverlicenses")
public class DriverLicense extends License{

 private String driverLicenseName;
 @Temporal(TemporalType.DATE)
 private Date driverLicenseExpiryDate;
 @Temporal(TemporalType.DATE)
 private Date driverLicenseIssueDate;

 public String getDriverLicenseName() {
 return driverLicenseName;
 }
 public void setDriverLicenseName(String driverLicenseName) {
 this.driverLicenseName = driverLicenseName;
 }
 public Date getDriverLicenseExpiryDate() {
 return driverLicenseExpiryDate;
 }
 public void setDriverLicenseExpiryDate(Date driverLicenseExpiryDate) {
 this.driverLicenseExpiryDate = driverLicenseExpiryDate;
 }
 public Date getDriverLicenseIssueDate() {
 return driverLicenseIssueDate;
 }
 public void setDriverLicenseIssueDate(Date driverLicenseIssueDate) {
 this.driverLicenseIssueDate = driverLicenseIssueDate;
 }

 @PrePersist
 public void prePersist(){
 System.out.println("DriverLicense Entity :: Method PrePersist Invoked Upon Entity "+this);
 }

}

The Result is


PersistenceContextListener :: Method PrePersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@74b957ea
PersistenceContextListener :: Method PrePersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Address@edc86eb
Address Entity :: Method PrePersist Invoked Upon Entity net.javabeat.eclipselink.data.Address@edc86eb
PersistenceContextListener :: Method PrePersist Invoked Upon Entity :: net.javabeat.eclipselink.data.DriverLicense@6f7918f0
DriverLicense Entity :: Method PrePersist Invoked Upon Entity net.javabeat.eclipselink.data.DriverLicense@6f7918f0
PersistenceContextListener :: Method PostPersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Address@edc86eb
PersistenceContextListener :: Method PostPersist Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@74b957ea
PersistenceContextListener :: Method PostPersist Invoked Upon Entity :: net.javabeat.eclipselink.data.DriverLicense@6f7918f0
PersistenceContextListener :: Method PreUpdate Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@74b957ea
PersistenceContextListener :: Method PostUpdate Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@74b957ea
PersistenceContextListener :: Method PreRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.DriverLicense@6f7918f0
License Entity :: Method PreRemove Invoked Upon Entity net.javabeat.eclipselink.data.DriverLicense@6f7918f0
PersistenceContextListener :: Method PreRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@74b957ea
PersistenceContextListener :: Method PreRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Address@edc86eb
PersistenceContextListener :: Method PostRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.DriverLicense@6f7918f0
License Entity :: Method PostRemove Invoked Upon Entity net.javabeat.eclipselink.data.DriverLicense@6f7918f0
PersistenceContextListener :: Method PostRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Employee@74b957ea
PersistenceContextListener :: Method PostRemove Invoked Upon Entity :: net.javabeat.eclipselink.data.Address@edc86eb

  • If you’ve tried to override a method without mentioning the same method name and the same callback type, you’re probably about getting a new method that will not be count a callback method override.

Summary

The Entity callback lifecycle methods is one of the important subject when you are trying to go deep in the JPA. This tutorial provide you the required definition and counts for the callback methods that could help you debugging and traversing the different operations of JPA.

Comments

comments

About Amr Mohammed

Speak Your Mind

*