springspring-boothibernatetomcat10

org.hibernate.jpa.boot.spi.IntegratorProvider are in unnamed module of loader org.apache.catalina.loader.ParallelWebappClassLoader


I am upgrading to spring boot 3.0 from 2.7 and tomcat 10 and I'm encountering the following error which has me stumped.

I know it usually means we have duplicate jars on the class path but I only have the one hibernate-core jar (version 6.1.7.Final). The full stack is below:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: class com.model.service.configuration.HibernateEventRegistry cannot be cast to class org.hibernate.jpa.boot.spi.IntegratorProvider (com.model.service.configuration.HibernateEventRegistry and org.hibernate.jpa.boot.spi.IntegratorProvider are in unnamed module of loader org.apache.catalina.loader.ParallelWebappClassLoader @4339e0de)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1762) ~[spring-beans-6.0.6.jar:6.0.6]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[spring-beans-6.0.6.jar:6.0.6]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[spring-beans-6.0.6.jar:6.0.6]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.6.jar:6.0.6]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.6.jar:6.0.6]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.6.jar:6.0.6]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.6.jar:6.0.6]

Below is my class:

package com.model.service.configuration;

import org.apache.logging.log4j.Logger;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;

public class HibernateEventRegistry implements Integrator {

    private static Logger logger = org.apache.logging.log4j.LogManager.getLogger(HibernateEventRegistry.class.getName());

    public static final HibernatePersistenceListener persistenceListener = new HibernatePersistenceListener();

    @Override
    public void integrate(Metadata metadata, BootstrapContext bootstrapContext, SessionFactoryImplementor sessionFactory) {
        logger.info("Registering hibernate event listeners...");
        final EventListenerRegistry eventRegistry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
        eventRegistry.prependListeners(EventType.POST_COMMIT_INSERT, persistenceListener);
        eventRegistry.prependListeners(EventType.POST_COMMIT_DELETE, persistenceListener);
        eventRegistry.prependListeners(EventType.POST_COMMIT_UPDATE, persistenceListener);
        eventRegistry.prependListeners(EventType.POST_COLLECTION_UPDATE, persistenceListener);
        eventRegistry.prependListeners(EventType.PRE_COLLECTION_UPDATE, persistenceListener);
        logger.info("Hibernate event listeners registered");
    }

    @Override
    public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {

    }
}

I've added spring.jpa.properties.hibernate.integrator_provider=com.model.service.configuration.HibernateEventRegistry to my application.properties to invoke the integrator.

Any ideas?

Edit:

I attempted another approach of writing it as a service and accessing the event registry from the entity manager (This approach worked fine in spring boot 2.7, hibernate 5, tomcat 8 and java 11).

package com.model.service.configuration;

import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManagerFactory;
import org.apache.logging.log4j.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.internal.SessionFactoryImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class HibernateEventRegistry {

    private static Logger logger = org.apache.logging.log4j.LogManager.getLogger(HibernateEventRegistry.class.getName());

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @Autowired
    private HibernatePersistenceListener persistenceListener;

    @PostConstruct
    public void registerListeners() {
        logger.info("Registering hibernate event listeners...");
        
        EventListenerRegistry eventRegistry = ((SessionFactoryImpl) entityManagerFactory.unwrap(SessionFactory.class)).getServiceRegistry()
                .getService(EventListenerRegistry.class);
        eventRegistry.prependListeners(EventType.POST_COMMIT_INSERT, persistenceListener);
        eventRegistry.prependListeners(EventType.POST_COMMIT_DELETE, persistenceListener);
        eventRegistry.prependListeners(EventType.POST_COMMIT_UPDATE, persistenceListener);
        eventRegistry.prependListeners(EventType.POST_COLLECTION_UPDATE, persistenceListener);
        eventRegistry.prependListeners(EventType.PRE_COLLECTION_UPDATE, persistenceListener);
        logger.info("Hibernate event listeners registered");
    }
}

But this produces:

Caused by: java.lang.ClassCastException: class jdk.proxy4.$Proxy288 cannot be cast to class org.hibernate.internal.SessionFactoryImpl (jdk.proxy4.$Proxy288 is in module jdk.proxy4 of loader org.apache.catalina.loader.ParallelWebappClassLoader @c2e3264; org.hibernate.internal.SessionFactoryImpl is in unnamed module of loader org.apache.catalina.loader.ParallelWebappClassLoader @c2e3264)
    at com.model.service.configuration.HibernateEventRegistry.registerListeners(HibernateEventRegistry.java:35) ~[?:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?]
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:424) ~[?:?]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:368) ~[?:?]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:192) ~[?:?]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:420) ~[?:?]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1754) ~[?:?]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[?:?]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[?:?]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[?:?]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[?:?]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[?:?]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[?:?]

There mus be something screwed up with my deployment but I cannot find it anywhere.


Solution

  • So to me it looks like spring boot 3 has an issue with hibernate Integrator. It's a simple pattern and falls short due to a similar issue as here - https://github.com/spring-projects/spring-framework/issues/26090

    I followed the instructions and was able to get the alternate flow to work by using unwrap against the implementation as opposed to the interface and then casting. Like below:

    EventListenerRegistry eventRegistry = entityManagerFactory.unwrap(SessionFactoryImpl.class).getServiceRegistry()
                    .getService(EventListenerRegistry.class);
    

    When compared to the previous line:

    EventListenerRegistry eventRegistry = ((SessionFactoryImpl) entityManagerFactory.unwrap(SessionFactory.class)).getServiceRegistry()
                    .getService(EventListenerRegistry.class);