JavaBeat

  • Home
  • Java
    • Java 7
    • Java 8
    • Java EE
    • Servlets
  • Spring Framework
    • Spring Tutorials
    • Spring 4 Tutorials
    • Spring Boot
  • JSF Tutorials
  • Most Popular
    • Binary Search Tree Traversal
    • Spring Batch Tutorial
    • AngularJS + Spring MVC
    • Spring Data JPA Tutorial
    • Packaging and Deploying Node.js
  • About Us
    • Join Us (JBC)
  • Privacy

JPA Entity Listeners And Callback Methods

March 12, 2014 by Amr Mohammed Leave a Comment

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

[code lang=”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);
}

}
[/code]

  • 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

[code lang=”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);
}

}

[/code]

License.java

[code lang=”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);
}
}

[/code]

  • 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

[code lang=”xml”]
<?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>
[/code]

Executable Application

[code lang=”java”]

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

}

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”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);
}

}

[/code]

The Result is

[code lang=”java”]

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

[/code]

  • 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.

Filed Under: Java EE Tagged With: EclipseLink, JPA

About Amr Mohammed

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Follow Us

  • Facebook
  • Pinterest

As a participant in the Amazon Services LLC Associates Program, this site may earn from qualifying purchases. We may also earn commissions on purchases from other retail websites.

JavaBeat

FEATURED TUTORIALS

Answered: Using Java to Convert Int to String

What is new in Java 6.0 Collections API?

The Java 6.0 Compiler API

Copyright © by JavaBeat · All rights reserved