I'm in the process of migrating my work's web application from a pure Hibernate persistence architecture over to a JPA-based architecture (with Hibernate as the JPA implementation provider). The application uses Spring XML context files for its configuration. Previously, the persistence-related configuration defined a couple of custom Hibernate event listeners, as Spring-defined beans, and these were passed along to Spring's
LocalSessionFactoryBean via its
eventListeners property in a straightforward manner:
<bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.annotation.LocalSessionFactoryBean">
...
<property name="eventListeners">
<map>
<entry key="post-load">
<list>
<bean ref="myPostLoadEventListenerBean" />
</list>
</map>
</property>
</bean>
However, after moving to a JPA-based configuration, and replacing the Hibernate-specific
LocalSessionFactoryBean with a JPA-specific
LocalContainerEntityManagerFactoryBean, the only way to declaratively specify the Hibernate event listeners is via the
jpaPropertyMap. However, with this method, the event listeners can no longer be Spring-instantiated objects, but rather can only be specified as class names, which Hibernate will "conveniently" use to instantiate the event listener objects on its own. This is hardly convenient, and in the case where one's custom event listener classes rely upon Spring's dependency injection for initialization, Hibernate's initialization mechanism is downright limiting. What we would like to do, but cannot, is specify our event listener beans within the JPA-compliant
jpaPropertyMap, as follows:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
<property name="persistenceProvider">
<bean class="org.hibernate.ejb.ConfigurableListenerBeansHibernatePersistence">
<property name="postLoadEventListeners">
<list>
<bean ref="myPostLoadEventListener" />
</list>
</property>
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.ejb.event.post-load">
<list>
<bean class="org.hibernate.ejb.event.EJB3PostLoadEventListener" />
<bean ref="myPostLoadEventListener" />
</list>
</entry>
</map>
</property>
</bean>
The limitation is ultimately introduced by Hibernate's
EventListenerConfigurator, which only knows how to handle event listener class names, rather than event listener objects. That's fine, I suppose, but Spring should provide some factory class of its own that allows the configuration of JPA provider-specific properties (such as Hibernate's event listeners), to get around this limitation by taking the desired beans and then programmatically updating the underlying
PersistenceProvider. But Spring does not provide this, as the
LocalContainerEntityManagerFactoryBean simply passes along the
jpaProperties without further consideration. In the end, Spring and Hibernate classes collude in a such a way that there is no way to accomplish what was easily done with the Hibernate-specific Spring configuration.
The solution I've come up with involves replacing Hibernate's
HibernatePersistence class with an extended version of the class,
ConfigurableListenerBeansHibernatePersistence, that specifically allows event listeners to be specified as objects, thus allowing Spring-instantiated bean to be injected. We then tell Spring's
LocalContainerEntityManagerFactoryBean to use our special
HibernatePersistence implementation, instead of the default
HibernatePersistence class. Yes, this sidesteps the JPA-compliant interfaces, but I find no other way to accomplish the required injection of event listeners. The new Spring XML configuration looks like this:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
...
<property name="persistenceProvider">
<bean class="org.hibernate.ejb.ConfigurableListenerBeansHibernatePersistence">
<property name="postLoadEventListeners">
<list>
<bean ref="myPostLoadEventListener" />
</list>
</property>
</bean>
</property>
<property name="jpaPropertyMap">
<map>
</map>
</property>
</bean>
Seems like Spring should provide a similar solution out-of-the-box.
Note that
ConfigurableListenerBeansHibernatePersistence also takes it upon itself to maintain the default event listeners that Hibernate normally creates on its own (
Ejb3*EventListeners and various
Default*EventListeners), and appends the application-provided listeners to these. Contrast this with the default configuration behavior, which requires a developer to specify the Hibernate-provided event listeners in addition to the listeners being added by the application. (Hibernate's approach is the most flexible, but a pain for developers that simply want to add new listeners that do not alter the underlying behavior of Hibernate.)
Finally, note that current version of
ConfigurableListenerBeansHibernatePersistence only provides configurability of the PostLoadEvent for now.