Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring EntityManager proxy is incompatible with Hibernate Search <5.11.6 (FullTextEntityManager instantiation fails with ClassCastException) #26090

Closed
knoobie opened this issue Nov 13, 2020 · 21 comments
Assignees
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: documentation A documentation task
Milestone

Comments

@knoobie
Copy link

knoobie commented Nov 13, 2020

After upgrading from 2.3.5.RELEASE to 2.4.0 instantiating a FullTextEntityManager (hibernate search) from an injected EntityManager is not possible anymore. The same code worked in the past and is derived from the official documentation or baeldung.

Code to reproduce:
https://github.com/knoobie/spring-2-4-entitymanager

How to check:

  • run the test; test fails on service#load
  • you can verify that it worked, if you just change the version to 2.3.5.RELEASE again and start the test (it doesn't fail)

Stacktrace:

java.lang.ClassCastException: com.sun.proxy.$Proxy98 cannot be cast to org.hibernate.engine.spi.SessionImplementor

	at org.hibernate.search.impl.FullTextSessionImpl.<init>(FullTextSessionImpl.java:62)
	at org.hibernate.search.impl.ImplementationFactory.createFullTextSession(ImplementationFactory.java:35)
	at org.hibernate.search.Search.getFullTextSession(Search.java:45)
	at org.hibernate.search.jpa.Search.getFullTextEntityManager(Search.java:49)
	at com.example.demo.search.MyService.load(MyService.java:51)
	at com.example.demo.search.MyService$$FastClassBySpringCGLIB$$195495fd.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:371)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:134)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
	at com.example.demo.search.MyService$$EnhancerBySpringCGLIB$$9e31f121.load(<generated>)
	at com.example.demo.DemoApplicationTests.contextLoads(DemoApplicationTests.java:20)
@wilkinsona
Copy link
Member

Thanks for the sample. I believe this change in behaviour is due to this change in Spring Framework 5.3.

The entityManagerInterface has changed from org.hibernate.jpa.HibernateEntityManager to org.hibernate.Session. This has a knock-on effect when Hibernate Search attempts to unwrap the EntityManager as a Session:

private static Session getSession(EntityManager em) {
    try {
        return em.unwrap( Session.class );
    }
    catch (PersistenceException e) {
        throw new SearchException(
                "Trying to use Hibernate Search with a non-Hibernate EntityManager", e
        );
    }
}

The EntityManager (a Spring Framework created proxy) now implements Session so the proxy is returned as-is. The proxy does not implement SessionImplementor so the subsequent cast fails. In Spring Framework 5.2 the proxy wasn't an instance of Session so a different code path was taken.

We'll transfer this to the Spring Framework team so that they can take a look.

@bclozel bclozel transferred this issue from spring-projects/spring-boot Nov 13, 2020
@bclozel bclozel added the status: waiting-for-triage An issue we've not yet triaged or decided on label Nov 13, 2020
@jhoeller jhoeller self-assigned this Nov 13, 2020
@jhoeller jhoeller added type: regression A bug that is also a regression in: data Issues in data modules (jdbc, orm, oxm, tx) and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Nov 13, 2020
@jhoeller jhoeller added this to the 5.3.2 milestone Nov 13, 2020
@knoobie
Copy link
Author

knoobie commented Nov 13, 2020

@wilkinsona thanks for you detailed analysis!

@jhoeller
Copy link
Contributor

This is arguably a mismatch in Hibernate Search where it calls EntityManager.unwrap(Session.class) but subsequently casts the result to SessionImplementor. It should really rather call EntityManager.unwrap(SessionImplementor.class) upfront if that's what it ultimately expects.

That said, I do see the point that previous unwrap(Session.class) calls did result in the native Session and could therefore be cast to the implementor interface at any point. Maybe we should simply use SessionImplementor as our EntityManager interface in HibernateJpaVendorAdapter...

@jhoeller jhoeller changed the title 2.4.0: Instantiating a FullTextEntityManager fails Hibernate Search fails to instantiate FullTextEntityManager (requires SessionImplementor interface to be exposed) Nov 13, 2020
@jhoeller
Copy link
Contributor

We're exposing SessionFactoryImplementor and SessionImplementor by default now, for all JPA proxies backed by HibernateJpaVendorAdapter. This will be available in the upcoming 5.3.2 snapshot, could you please give it a try?

@knoobie
Copy link
Author

knoobie commented Nov 13, 2020

@jhoeller I've updated my example to use the latest 5.3.2-SNAPSHOT from master. Now the exception changes to the following:


java.lang.ClassCastException: com.sun.proxy.$Proxy97 cannot be cast to org.hibernate.event.spi.EventSource

	at org.hibernate.search.impl.FullTextSessionImpl.<init>(FullTextSessionImpl.java:66)
	at org.hibernate.search.impl.ImplementationFactory.createFullTextSession(ImplementationFactory.java:35)
	at org.hibernate.search.Search.getFullTextSession(Search.java:45)
	at org.hibernate.search.jpa.Search.getFullTextEntityManager(Search.java:49)
	at com.example.demo.search.MyService.load(MyService.java:51)
	at com.example.demo.search.MyService$$FastClassBySpringCGLIB$$195495fd.invoke(<generated>)

Coming from the other cast inside hibernate search.

image

Edit: Changing the HibernateJpaVendorAdapter to use org.hibernate.event.spi.EventSource instead of SessionImplementor worked for me. But I'm no hibernate expert to say if that's a good solution.

@jhoeller jhoeller changed the title Hibernate Search fails to instantiate FullTextEntityManager (requires SessionImplementor interface to be exposed) Hibernate Search fails to instantiate FullTextEntityManager (requires SessionImplementor and EventSource interfaces to be exposed) Nov 13, 2020
@jhoeller
Copy link
Contributor

Argh, another unguarded cast there! EventSource is even worse since it's too internal for HibernateJpaVendorAdapter to use it as a default EntityManager interface. I'll rather change our strategy then, reverting to plain Session as default EntityManager interface but handling unwrap calls differently, leaning towards returning the underlying native instance more eagerly.

Thanks for the quick turnaround, in any case! I'll try the other approach right away, will ping you once it's ready in a shapshot.

@jhoeller jhoeller reopened this Nov 13, 2020
@jhoeller
Copy link
Contributor

This is a quite subtle issue. Our differentiated handling of unwrap calls has use cases where the proxy needs to be returned, and Session really is Hibernate's EntityManager extension now (they deprecated their old HibernateEntityManager interface), so it seems sensible to expose it at the proxy level as far as possible.

Frankly, Hibernate Search should call unwrap(SessionImplementor.class) there if it knows that it applies further downcasts later on. As of Hibernate 5.2+ where Session is an EntityManager extension, their old unwrap(Session.class) is arguably not semantically sufficient anymore. This is worth reporting to them.

I'll keep trying further what we can do about it from our end. Ideally we'd also remain compatible with older Hibernate Search versions, even if this gets refined in the Search implementation.

@yrodiere
Copy link
Contributor

I'll try to replace the casts with calls to unwrap in Search 5, and I think we already did in Search 6.

One important detail: can you confirm that calling .unwrap(Session.class) used to unproxy the entity manager, and that calling .unwrap(SessionImplementor.class) will do the same, both in Spring 2.3 and 2.4? I'd rather not change the behavior for existing users...

@jhoeller
Copy link
Contributor

Hey @yrodiere, thanks for chiming in! Indeed, unwrap(SessionImplementor.class) is going to do exactly the same against older versions of Spring since we're using an instanceof check for our proxy there, and the proxy neither implemented Session nor SessionImplementor back then, so we'd always delegate to the native EntityManager.unwrap method underneath.

@yrodiere
Copy link
Contributor

@jhoeller Thanks for confirming this.

For the record, here are the relevant PRs in Hibernate Search:

I'll try to release 5.11 early next week.

@jhoeller
Copy link
Contributor

Great news, @yrodiere - I'll leave our unwrap handling as-is then, updating our 5.3 migration notes accordingly.

@jhoeller jhoeller added type: documentation A documentation task and removed type: regression A bug that is also a regression labels Nov 16, 2020
@jhoeller jhoeller changed the title Hibernate Search fails to instantiate FullTextEntityManager (requires SessionImplementor and EventSource interfaces to be exposed) Spring EntityManager proxy is incompatible with Hibernate Search <5.11 (fails to instantiate FullTextEntityManager) Nov 16, 2020
@jhoeller jhoeller changed the title Spring EntityManager proxy is incompatible with Hibernate Search <5.11 (fails to instantiate FullTextEntityManager) Spring EntityManager proxy is incompatible with Hibernate Search <5.11 (FullTextEntityManager instantiation fails with ClassCastException) Nov 16, 2020
@jhoeller jhoeller changed the title Spring EntityManager proxy is incompatible with Hibernate Search <5.11 (FullTextEntityManager instantiation fails with ClassCastException) Spring EntityManager proxy is incompatible with Hibernate Search <5.11.6 (FullTextEntityManager instantiation fails with ClassCastException) Nov 16, 2020
@baron1405
Copy link

FYI I had experienced this issue and just updated to Hibernate Search 5.11.6. Happy to report that Spring and Hibernate Search are once again happy with each other. Thanks to all who worked on identifying and fixing the issue!

@knoobie
Copy link
Author

knoobie commented Nov 17, 2020

I can confirm it as well - my application is running with Hibernate Search 5.11.6 and Spring Boot 2.4.0 without any issues. Thanks for your help!

@Toan1606
Copy link

@knoobie I copy your code in github link and still has exception

java.lang.ClassCastException: class jdk.proxy4.$Proxy100 cannot be cast to class org.hibernate.engine.spi.SessionImplementor (jdk.proxy4.$Proxy100 is in module jdk.proxy4 of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @3ed2eee9; org.hibernate.engine.spi.SessionImplementor is in unnamed module of loader 'app')
at org.hibernate.search.impl.FullTextSessionImpl.(FullTextSessionImpl.java:61) ~[hibernate-search-orm-5.8.2.Final.jar:5.8.2.Final]
at org.hibernate.search.impl.ImplementationFactory.createFullTextSession(ImplementationFactory.java:34) ~[hibernate-search-orm-5.8.2.Final.jar:5.8.2.Final]
at org.hibernate.search.Search.getFullTextSession(Search.java:44) ~[hibernate-search-orm-5.8.2.Final.jar:5.8.2.Final]
at org.hibernate.search.jpa.Search.getFullTextEntityManager(Search.java:48) ~[hibernate-search-orm-5.8.2.Final.jar:5.8.2.Final]
at com.example.demo.controller.PostController.fullTextSearch(PostController.java:36) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.21.jar:5.3.21]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.21.jar:5.3.21]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.21.jar:5.3.21]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.21.jar:5.3.21]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.21.jar:5.3.21]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.21.jar:5.3.21]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[spring-webmvc-5.3.21.jar:5.3.21]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.21.jar:5.3.21]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.21.jar:5.3.21]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.21.jar:5.3.21]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[tomcat-embed-core-9.0.64.jar:4.0.FR]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.21.jar:5.3.21]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.64.jar:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.64.jar:9.0.64]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.21.jar:5.3.21]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.21.jar:5.3.21]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.21.jar:5.3.21]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.21.jar:5.3.21]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.21.jar:5.3.21]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.21.jar:5.3.21]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1787) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.64.jar:9.0.64]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

Can you let me know how to fix this code ?
Exception

@knoobie
Copy link
Author

knoobie commented Jul 14, 2022

@Toan1606 if you update your dependencies, the problem is gone. Never had any problems since this was fixed. All Spring Framework / Boot and Hibernate Search versions afterwards work perfectly fine.

@Toan1606
Copy link

SpringBootVersion
it must be done in springboot version 2.4 right?

@Toan1606
Copy link

I am testing it in spring boot version 2.7.1

@knoobie
Copy link
Author

knoobie commented Jul 14, 2022

Please check the Hibernate Search version. You are using an outdated and unsupported version. The fix was introduced in Hibernate Search 5.11.6. If you are fresh starting a project, I would recommend to start with Hibernate Search 6 as well.

@Toan1606
Copy link

Exception
When I change HIbernate Search to 5.11.6

@Toan1606
Copy link

My project is fixed in Hibernate Search 5

@Toan1606
Copy link

Is the code in your github link sure it was running before?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

7 participants