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).
- PrePersist: This callback method is invoked for a given entity before the respective Entity Manager executes persist operation for that entity.
- PostPersist: This callback method is invoked for a given entity after the respective Entity Manager executes persist operation for that entity.
- PreRemove: This callback method is invoked for a given entity before the respective Entity Manager executes remove operation for that entity.
- PostRemove: This callback method is invoked for a given entity after the respective Entity Manager executes remove operation for that entity.
- PreUpdate: This callback method is invoked for a given entity before the respective Entity Manager executes merge operation for that entity.
- PostUpdate: This callback method is invoked for a given entity after the respective Entity Manager executes merge operation for that entity.
- 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.