Using Hibernate 4's Integrator pattern and Spring's dependency injection
Asked Answered
V

2

10

I'm used to using Spring to do my dependency injection like so:

<context:component-scan base-package="org.emmerich.myapp" />

and then annotating my dependent classes with Autowired like so:

public class DependentClass {

    @Autowired
    private Dependency dependency;

}

However, with the changes in Hibernate 4.0, we're now advised to use the new Integrator interface for service discovery. This includes adding event listeners for triggers such as postUpdate, postDelete etc.

Unfortunately, this doesn't play nicely with dependency injection through annotated dependencies. I have the following setup:

An integrator I have defined to add my listener to the ServiceFactory. This is referenced in the file META-INF/services/org.hibernate.integrator.spi.Integrator.

public class MyIntegrator implements Integrator {

    private MyListener listener;

    public MyIntegrator() {
        listener = new MyListener();
    }

    @Override
    public void integrate(Configuration configuration,
                          SessionFactoryImplementor sessionFactory,
                          SessionFactoryServiceRegistry serviceRegistry) {
    final EventListenerRegistry eventRegistry =
        serviceRegistry.getService(EventListenerRegistry.class);

    eventRegistry.prependListeners(EventType.POST_COMMIT_INSERT, listener);

}

I also have defined the class MyListener, which looks like your typical event listener.

@Component
public class MyListener implements PostInsertEventListener {

    @Autowired
    private Dependent dependent;

    public void onPostInsert(PostInsertEvent event) {
         // dependent == null
    }

}

Unforunately, as shown by the comment, this doesn't work. I guess it's because I'm instantiating MyListener inside MyIntegrator, it doesn't pick up the component and doesn't autowire components. However, if I try this:

@Component
public class MyIntegrator {

     @Autowired
     private MyListener listener;

     ...
}

Then the listener isn't autowired.

Firstly, it feels wrong whilst using Spring to have to do new MyListener(). I expect to be able to define that as an autowired dependency and have Spring create a singleton for me. My question is this:

What's the best approach to using dependency injection with the new Integrator interface? The Integrators are used to build a SessionFactory, and so when they're asked to integrate themselves I guess there isn't an application context available. Because of that, any beans I require in the Integrator need to be created the "old fashioned" way and won't receive the autowiring on them.

I'm quite new to the world of Spring, would you say this is something that I should expect to see? I understand that I'm in a different scope of the application when I'm in the SessionFactory, but is there a way to obtain a reference to the bean and enable autowire even though I'm creating it via new?

The solution I came up with used ApplicationContextAware. It meant that MyListener received a reference to the ApplicationContext whenever the context was available, and I referenced the beans from the context on method calls, rather than on bean construction. Creating a bean with new doesn't limit this, so Spring still gives me the application context:

@Component
public class MyListener implements PostInsertEventListener, ApplicationContextAware {

    private static ApplicationContext context;

    public void onPostInsert(PostInsertEvent event) {
         // getDependent() == correct!
    }

    public void setApplicationContext(ApplicationContext context) throws BeanException {
        this.context = context;
    }

    public Dependent getDependent() {
        return context.getBean(Dependent.class);
    }

}

Is there a better way?

Vintager answered 15/4, 2013 at 16:7 Comment(5)
You didn't forget to component-scan MyIntegrator class in your @Autowired example? Also, who is instantiating the MyIntegrator class?Monsour
This is exactly the same solution i came up with too. Its working nice but the static applicationcontext just feels a bit dirty :) i just did not find a nicer way since hibernate is creating the Integrator. Once i will have time i want to make this more abstract and have one integrator which is just a link to be able to register listeners etc created by spring. I found a blog once but i cannot remember the url right now.Nabala
@SotiriosDelimanolis Hibernate creates the Integrator for you. You specify a text file in your META-INF/services directory pointing to the list of integrators you want instantiated, and Hibernate builds them for you before creating the SessionFactory. I kind of understand not being able to Autowire components in the Integrator - it's constructed outside of the application's scope. However, I thought it'd be possible to use @Autowired in the listener. And yes, I'm context scanning all classes involved.Vintager
@MartinFrey Yep, I know what you mean. I did think about having just one integrator but it didn't feel right. Hibernate seems to want you to specify various integrators to decorate your SessionFactory. If you get the blog link, let me know.Vintager
I just rechecked my implementation and i finally went another way. Basically i autowire the HibernateEntityManagerFactory in my integrator and user the serviceregistry to implement my listeners. This way is completely Spring like. I post my code as an answer.Nabala
N
14

As stated in the comment i went another way of integrating Spring managed HibernateEventListeners. Here's the code:

The identifier interface for Spring managed Hibernate event listeners:

public interface HibernateEventListener { }

The HibernateIntegrator:

@Service
public class HibernateSpringIntegrator {

    private static final Logger log = LoggerFactory.getLogger(HibernateSpringIntegrator.class);

    @Autowired
    private HibernateEntityManagerFactory entityManagerFactory;

    @Autowired
    private HibernateSpringIntegratorRegistry hibernateSpringIntegratorRegistry;

    @PostConstruct
    public void registerListeners() {
        log.debug("Registering Spring managed HibernateEventListeners");

        EventListenerRegistry listenerRegistry = ((SessionFactoryImpl) entityManagerFactory
                .getSessionFactory()).getServiceRegistry().getService(
                EventListenerRegistry.class);
        List<HibernateEventListener> eventListeners = hibernateSpringIntegratorRegistry
                .getHibernateEventListeners();
        for (HibernateEventListener hel : eventListeners) {
            log.debug("Registering: {}", hel.getClass());
            if (PreInsertEventListener.class.isAssignableFrom(hel.getClass())) {
                listenerRegistry.appendListeners(EventType.PRE_INSERT,
                        (PreInsertEventListener) hel);
            }
            if (PreUpdateEventListener.class.isAssignableFrom(hel.getClass())) {
                listenerRegistry.appendListeners(EventType.PRE_UPDATE,
                        (PreUpdateEventListener) hel);
            }
            if (PreDeleteEventListener.class.isAssignableFrom(hel.getClass())) {
                listenerRegistry.appendListeners(EventType.PRE_DELETE,
                        (PreDeleteEventListener) hel);
            }
            if (PostInsertEventListener.class.isAssignableFrom(hel.getClass())) {
                listenerRegistry.appendListeners(EventType.POST_INSERT,
                        (PostInsertEventListener) hel);
            }
            if (PostUpdateEventListener.class.isAssignableFrom(hel.getClass())) {
                listenerRegistry.appendListeners(EventType.POST_UPDATE,
                        (PostUpdateEventListener) hel);
            }
            if (PostDeleteEventListener.class.isAssignableFrom(hel.getClass())) {
                listenerRegistry.appendListeners(EventType.POST_DELETE,
                        (PostDeleteEventListener) hel);
            }
            // Currently we do not need other types of eventListeners. Else this method needs to be extended.
        }
    }
}

The "Registry":

@Component
public class HibernateSpringIntegratorRegistry {

    @Autowired(required = false)
    private List<HibernateEventListener> hibernateEventListeners;

    public List<HibernateEventListener> getHibernateEventListeners() {
        if (hibernateEventListeners == null) {
            return Collections.emptyList();
        }
        return hibernateEventListeners;
    }
}

And here's an example implementation:

@Component
public class MailGenerationEventListener implements HibernateEventListener, 
    PostDeleteEventListener, PostInsertEventListener, PostUpdateEventListener {

    @Override
    public void onPostDelete(PostDeleteEvent event) {
        Class<?> entityClass = event.getEntity().getClass();
        ...
    }

    @Override
    public void onPostInsert(PostInsertEvent event) {
        Class<?> entityClass = event.getEntity().getClass();
        ...
    }

    @Override
    public void onPostUpdate(PostUpdateEvent event) {
        Class<?> entityClass = event.getEntity().getClass();
        ...
    }
}
Nabala answered 16/4, 2013 at 19:17 Comment(6)
Interesting response. Is this an alternative to implementing the Integrator interface and defining the integrator text file in META-INF/services?Vintager
I guess the issue I have with this is that it doesn't follow the Integrator pattern, rather it provides a custom Springified way of attaching event listeners.Vintager
Yes this is a completly Springifed implementation. I think one could go the way of implementing the HibernateSpringIntegrator as a "real" integrator service using ApplicationContextAware and fetching the HibernateSpringIntegratorRegistry. This way the integrator is Hibernate like with a static applicationcontext reference but is still able to use simple autowiring for the eventlisteners through the registryNabala
Martin, can you show an example of what you mentioned above. I have an integrator set up to log hibernate save, updates, and deletes. I would like to expand it and save this information to a database based on the spring security principal (logged in user). I am confused how I can get access to that object from the integrator pattern. Is it possible? Or should I implement a solution like the one you posted above?Waterman
@Waterman i dont have a complete example but basically you can implements a class which is ApplicationContextAware containing a static reference to the applicationContext. In the integrator you reference this class and use that static reference to fetch the eventlisteners and bind them.Nabala
Thank you Martin Frey for the wiring. In the onPostDelete method i am trying to execute an update operation but i am getting and AssertionFailure exception when i call session.flush() "org.hibernate.AssertionFailure: possible nonthreadsafe access to session". any idea why? i am trying to get this call back to work but keep getting the same error.Tigges
D
-1

During an upgrade from hibernate 3.6 to 4.2, we needed to have a custom validator that uses spring-managed beans by doing the following configuration:

<!-- Make our validators use DI if necessary -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <!-- other props -->
    <property name="hibernateProperties">
            <map>
                <entry key="javax.persistence.validation.factory" value-ref="validator" />
            </map>
    </property>
</bean>
Duress answered 17/3, 2014 at 17:42 Comment(1)
How is this relevant?Collett

© 2022 - 2024 — McMap. All rights reserved.