Spring-managed Hibernate Listeners with JPA

September 28, 2011

A standard use-case – you need an entity listener in order to execute some code on every update/insert/delete. For auditing, for example. But things are not straightforward if you need spring dependencies in your listeners and you are using JPA.

First of all, the JPA-only listeners are insufficient – you can annotate a method with @PreUpdate, but the most you can get as context is the entity it is about (documentation). But you may need the old values. Or the extracted ID of the entity. Or other metadata. All of that is not supported by JPA. So you need to implement hibernate interfaces like PreDeleteEventListener, PreUpdateEventListener, PostInsertEventListener, etc. and get their XEvent objects.

But you can’t easily have these listeners both spring-managed and registered if you are using JPA. You can list them as class names in some hibernate-specific property in persistence.xml, but that way hibernate will instantiate them. Below is the tweaks you need to make in order to get this working:

First, extend the persistence provider:

public class HibernateExtendedPersistenceProvider extends HibernatePersistence {

    private PostInsertEventListener[] postInsertEventListeners;
    private PreUpdateEventListener[] preUpdateEventListeners;
    private PreDeleteEventListener[] preDeleteEventListeners;
    
    @SuppressWarnings("rawtypes")
    @Override
    public EntityManagerFactory createEntityManagerFactory(String persistenceUnitName, Map properties) {
        Ejb3Configuration cfg = new Ejb3Configuration();
        setupConfiguration(cfg);
        Ejb3Configuration configured = cfg.configure( persistenceUnitName, properties );
        return configured != null ? configured.buildEntityManagerFactory() : null;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
        Ejb3Configuration cfg = new Ejb3Configuration();
        setupConfiguration(cfg);
        Ejb3Configuration configured = cfg.configure( info, properties );
        return configured != null ? configured.buildEntityManagerFactory() : null;
    }
    
    private void setupConfiguration(Ejb3Configuration cfg) {
        cfg.getEventListeners().setPostInsertEventListeners(postInsertEventListeners);
        cfg.getEventListeners().setPreDeleteEventListeners(preDeleteEventListeners);
        cfg.getEventListeners().setPreUpdateEventListeners(preUpdateEventListeners);
        //TODO if others are needed - add them
    }

    public void setPostInsertEventListeners(PostInsertEventListener[] postInsertEventListeners) {
        this.postInsertEventListeners = postInsertEventListeners;
    }

    public void setPreUpdateEventListeners(PreUpdateEventListener[] preUpdateEventListeners) {
        this.preUpdateEventListeners = preUpdateEventListeners;
    }

    public void setPreDeleteEventListeners(PreDeleteEventListener[] preDeleteEventListeners) {
        this.preDeleteEventListeners = preDeleteEventListeners;
    }
}

Then annotate your listener(s) with @Component (or declare them as spring beans the way you prefer). Then register them:

<bean id="hibernatePersistenceProvider" class="com.foo.bar.configuration.HibernateExtendedPersistenceProvider">
		<property name="postInsertEventListeners">
			<list>
				<ref bean="hibernateAuditLogListener" />
			</list>
		</property>
		<property name="preUpdateEventListeners">
			<list>
				<ref bean="hibernateAuditLogListener" />
			</list>
		</property>
		<property name="preDeleteEventListeners">
			<list>
				<ref bean="hibernateAuditLogListener" />
			</list>
		</property>
	</bean>

And finally, set the customized persistence provider to the entity manager factory bean:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    	<property name="persistenceProvider" ref="hibernatePersistenceProvider" />

What you just did:

  • Made use of the fact that the PersistenceProvider allows you to obtain the hibernate Configuration object, which is not otherwise accessible when working with JPA
  • Registered your listeners as spring beans and added them to the extended persistence provider, which in turn registers them with hibernate
  • set the “persistenceProvider” property of spring’s LocalContainerEntityManagerFactoryBean. Normally you don’t set that, because it is inferred from the vendor adapter or from the classpath.
If you find the content interesting, you can subscribe and get updates


 

6 Responses to “Spring-managed Hibernate Listeners with JPA”

  1. Wow dude, thank you very much, this post was very very useful!

  2. [...] From http://techblog.bozho.net/?p=600 [...]

  3. Could you please share us an example on writing a event listener to listen on Lazy loaded entites

  4. I don’t know of such feature. If you find it – let me know.

  5. but cfg.getEventListeners() is not available anymore in Hibernate 4. Now Im trying to use this new Integrator approach ( see half way down this page http://stackoverflow.com/questions/8616146/eventlisteners-using-hibernate-4-0-with-spring-3-1-0-release)

    problem again though is my CustomIntegrator is managed by hibernate so I cannot configure a listener that has a preinjected dao into it (and i need a spring managed dao to do some db stuff on events)

    damn hibernate is all cloaks and daggers

  6. Just the implementation i was searching for about a week. Nice one

Leave a Reply