Adding multiple EntityListeners to an Entity in JPA 2
February 25th, 2013 by Micha KopsThe 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.
Contents
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 “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 Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/jpa-entitylistener-tutorial.git
Other JPA Articles
Please feel free to have a look at my other articles about the Java Persistence API:
- Java Persistence API: Controlling the Second-Level-Cache
- Creating slim Database Projections using JPA2 Constructor Expressions
- Object-relational Mapping using Java Persistence API / JPA 2
Resources
Article Updates
- 2015-03-03: Links to my other JPA articles added.
Tags: entitylistener, entitymanager, hibernate, java persistence api, jpa, orm, persistence