The ability to attach lifecycle events to an entity using simple annotations sometimes is a neat feature in the Java Persistence API.
The following short snippets demonstrate how to bind and trigger the different available lifecycle events using an embedded derby database and a bunch of annotations.
Dependencies
I’m using Hibernate as persistence manager here and Derby as an easy to setup database. In the last step we’ll be writing a test that’s why we’ve added JUnit and Hamcrest.
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<derby.version>10.9.1.0</derby.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>${derby.version}</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>${derby.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.9.Final</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
Creating an Entity
First of all we’re creating the classical JPA entity .. a book entit that has and numeric ID, a title and a date field.
Please notice that we’ve registered two entity listeners for this entity using the @EntityListeners annotation for the class.
package com.hascode.tutorial.entity;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import com.hascode.tutorial.entity.listener.BookEntityListener;
import com.hascode.tutorial.entity.listener.TitleValidator;
@Entity
@EntityListeners({ BookEntityListener.class, TitleValidator.class })
public class Book implements TitleEntity {
@Id
@GeneratedValue
private Long id;
private String title;
@Temporal(TemporalType.DATE)
private Date published;
public final Long getId() {
return id;
}
public final void setId(final Long id) {
this.id = id;
}
@Override
public final String getTitle() {
return title;
}
public final void setTitle(final String title) {
this.title = title;
}
public final Date getPublished() {
return published;
}
public final void setPublished(final Date published) {
this.published = published;
}
@Override
public String toString() {
return "Book [id=" + id + ", title=" + title + ", published="
+ published + "]";
}
}
Afterwards we should reference the entity in the persistence.xml in src/test/resources/META-INF
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
version="1.0">
<persistence-unit name="hascode-tutorial"
transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.hascode.tutorial.entity.Book</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="javax.persistence.jdbc.url" value="jdbc:derby:hascode_testdb;create=true" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
</properties>
</persistence-unit>
</persistence>
You might have noticed that our entity implements an interface so this is the TitleEntity interface .. it just got one getter method, getTitle()
package com.hascode.tutorial.entity;
public interface TitleEntity {
String getTitle();
}
EntityListeners
Now the final part – our entity listeners: The first entity listener responds to every possible entity lifecycle listener and prints out a message.
The following lifecycle events are supported:
-
@PrePersist: triggered before a new entity is persisted (being added to the EntityManager)
-
@PostPersist: – triggered after storing a new entity in the database (during commit or flush).
-
@PostLoad: – triggered after an entity has been retrieved from the database.
-
@PreUpdate: – triggered when an entity is identified as modified by the EntityManager.
-
@PostUpdate: – triggered after updating an entity in the database (during commit or flush).
-
@PreRemove: – triggered when an entity is marked for removal in the EntityManager.
-
@PostRemove: – triggered after deleting an entity from the database (during commit or flush).
package com.hascode.tutorial.entity.listener;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import com.hascode.tutorial.entity.Book;
public class BookEntityListener {
@PrePersist
public void prePersist(final Book book) {
System.out.println("prePersist: " + book.toString());
}
@PostPersist
public void postPersist(final Book book) {
System.out.println("postPersist: " + book.toString());
}
@PreUpdate
public void preUpdate(final Book book) {
System.out.println("preUpdate: " + book.toString());
}
@PostUpdate
public void postUpdate(final Book book) {
System.out.println("postUpdate: " + book.toString());
}
@PostLoad
public void postLoad(final Book book) {
System.out.println("postLoad: " + book.toString());
}
@PreRemove
public void preRemove(final Book book) {
System.out.println("preRemove: " + book.toString());
}
@PostRemove
public void postRemove(final Book book) {
System.out.println("postRemove: " + book.toString());
}
}
The second entity listener is activated before an entity gets persisted and validates the title of the book against a list of bad words.
Please notice that you really should not validate your entities this way – its just for demonstration purpose – if you’re interesting in an efficient way to validate your entities, please feel free to take a look at my tutorial “https://www.hascode.com/2010/12/bean-validation-with-jsr-303-and-hibernate-validator/[Bean Validation with JSR-303 and Hibernate Validator]“.
package com.hascode.tutorial.entity.listener;
import javax.persistence.PersistenceException;
import javax.persistence.PrePersist;
import com.hascode.tutorial.entity.TitleEntity;
public class TitleValidator {
private final String[] badWords = { "shitty", "xxx", "..." };
@PrePersist
public void checkBadWords(final TitleEntity titleEntity) {
for (String badWord : badWords) {
if (titleEntity.getTitle().contains(badWord)) {
System.err.println("bad word in title detected: " + badWord);
throw new PersistenceException("bad word in title detected: "
+ badWord);
// should use ValidationException if bean validation is present
}
}
}
}
Testing
Now that we’ve got everything we need – the last thing is to put it all together and persist some data in our embedded database.
You might be wondering why I’m flushing the entitymanager all the time – this is solely to demonstrate all possible life-cycle events.
package it;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import java.sql.SQLException;
import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.hascode.tutorial.entity.Book;
public class BookIT {
static EntityManagerFactory emf;
static EntityManager em;
static EntityTransaction tx;
@BeforeClass
public static void setupClass() throws Exception {
emf = Persistence.createEntityManagerFactory("hascode-tutorial");
em = emf.createEntityManager();
tx = em.getTransaction();
}
@Before
public void setUp() {
tx.begin();
}
@AfterClass
public static void teardownClass() throws SQLException {
em.close();
emf.close();
}
@After
public void tearDown() {
tx.rollback();
}
@Test(timeout = 10000)
public void shouldTriggerBookLifecycles() throws Exception {
Book book = new Book();
book.setPublished(new Date());
book.setTitle("Some book");
em.persist(book);
assertThat(book.getId(), is(notNullValue()));
assertThat(book.getId(), is(1L));
book.setPublished(new Date());
em.persist(book);
em.flush();
em.refresh(book); // we just want to trigger postLoad
Book book2 = em.find(Book.class, 1L);
assertThat(book2, is(notNullValue()));
assertThat(book2.getId(), is(book.getId()));
assertThat(book2.getTitle(), is(book.getTitle()));
em.remove(book2);
em.flush();
}
@Test(timeout = 10000, expected = PersistenceException.class)
public void testShouldValidateTitle() throws Exception {
Book book = new Book();
book.setPublished(new Date());
book.setTitle("The shitty boook");
em.persist(book);
}
}
The first test produces some similar output:
prePersist: Book [id=null, title=Some book, published=Mon Feb 25 21:55:12 CET 2013]
postPersist: Book [id=1, title=Some book, published=Mon Feb 25 21:55:12 CET 2013]
postLoad: Book [id=1, title=Some book, published=2013-02-25]
preRemove: Book [id=1, title=Some book, published=2013-02-25]
postRemove: Book [id=1, title=Some book, published=2013-02-25]
The second test yields the following output:
bad word in title detected: shitty
Tutorial Sources
Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:
git clone https://github.com/hascode/jpa-entitylistener-tutorial.git
Other JPA Articles
Please feel free to have a look at my other articles about the Java Persistence API:
Article Updates
-
2015-03-03: Links to my other JPA articles added.