SQL Zone is brought to you in partnership with:

Senior Java developer, one of the top stackoverflow users, fluent with Java and Java technology stacks - Spring, JPA, JavaEE. Founder and creator of http://computoser.com and http://welshare.com . Worked on Ericsson projects, Bulgarian e-government projects and large scale recruitment platforms. Member of the jury of the International Olympiad in Linguistics and the Program committee of the North American Computational Linguistics Olympiad. Bozhidar is a DZone MVB and is not an employee of DZone and has posted 82 posts at DZone. You can read more from them at their website. View Full User Profile

Spring-managed Hibernate Listeners with JPA

09.30.2011
| 10126 views |
  • submit to reddit

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.

 

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

Published at DZone with permission of Bozhidar Bozhanov, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Simon Turner replied on Mon, 2011/10/10 - 3:33am

Or you could just use load time weaving to get Spring to inject into any @Configurable bean, including ones instantiated outside of Spring (e.g. by Hibernate). Also, this has become easier and less intrusive recently - current versions of most application servers offer classloader support for this without having to define a jvm-wide -javaagent. Is there any reason you have discounted this approach for JPA?

Data Mining replied on Fri, 2013/11/15 - 9:35am

Ejb3Configuration has been deprecated. Is there a work arround this currently?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.