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.
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);