Adding multiple EntityListeners to an Entity in JPA 2

February 25th, 2013 by

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 “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:

Resources

Article Updates

  • 2015-03-03: Links to my other JPA articles added.

    Tags: , , , , , ,

    Leave a Reply

    Please note, that no personal information like your IP address is stored and you're not required to enter you real name.

    Comments must be approved before they are published, so please be patient after having posted a comment - it might take a short while.

    Please leave these two fields as-is:
    Search
    Tags
    Categories