Seems like AOP transaction interceptor fails to start an active transaction when the session factory is wrapped in a proxy.
It doesn't appear like the AOP transaction interceptor works when a proxy is involved. I removed my custom session factory to test as simply as possible.
I have a sessionfactory:
<bean name="demoSessionFactory" id="demoSessionFactory" parent="abstractSessionFactory"> <property name="dataSource" ref="demoDataSource"/> </bean>
If I inject it directly into the transaction manager like so everything works:
```
``` However, if I instead wrap it in a TargetSource to proxy it fails.
<bean id="sessionFactoryTarget" > class="com.shavara.stripeinternalispayment.config.TestTargetSource"> <property name="sessionFactory" ref="demoSessionFactory"/> </bean> <bean id="sessionFactory" name="sessionFactory, entityManagerFactory" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="targetSource" ref="sessionFactoryTarget"/> <property name="proxyInterfaces" value="org.hibernate.SessionFactory"/> </bean>
Here is the target source code:``` public class TestTargetSource implements TargetSource { private SessionFactory sessionFactory;
public TestTargetSource() { } public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } @Override public Class<?> getTargetClass() { return SessionFactory.class; } @Override public Object getTarget() throws Exception { return sessionFactory; } }
```
This is the error I get:
jakarta.persistence.TransactionRequiredException: no transaction is in progress at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:560) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final] at org.hibernate.internal.SessionImpl.checkTransactionNeededForUpdateOperation(SessionImpl.java:2564) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final] at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1420) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final] at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1415) ~[hibernate-core-6.6.11.Final.jar:6.6.11.Final] at org.springframework.orm.hibernate5.SessionFactoryUtils.flush(SessionFactoryUtils.java:113) ~[spring-orm-6.2.5.jar:6.2.5] at org.springframework.orm.hibernate5.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:99) ~[spring-orm-6.2.5.jar:6.2.5] at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:127) ~[spring-tx-6.2.5.jar:6.2.5] at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:986) ~[spring-tx-6.2.5.jar:6.2.5] at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:775) ~[spring-tx-6.2.5.jar:6.2.5] at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:758) ~[spring-tx-6.2.5.jar:6.2.5] at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:698) ~[spring-tx-6.2.5.jar:6.2.5] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:416) ~[spring-tx-6.2.5.jar:6.2.5] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.5.jar:6.2.5] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.5.jar:6.2.5] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.5.jar:6.2.5] at com.test.service.implementations.PatientPaymentImpl$$SpringCGLIB$$0.getValidSetupIntent(<generated>) ~[classes/:na] at com.test.paymentmethodrequest.PaymentMethodController.home(PaymentMethodController.java:47) ~[classes/:na] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na] at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) ~[spring-web-6.2.5.jar:6.2.5] at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) ~[spring-web-6.2.5.jar:6.2.5] at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.2.5.jar:6.2.5] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) ~[spring-webmvc-6.2.5.jar:6.2.5] at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) ~[spring-webmvc-6.2.5.jar:6.2.5] at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.2.5.jar:6.2.5] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.2.5.jar:6.2.5] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.2.5.jar:6.2.5] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.2.5.jar:6.2.5] at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.2.5.jar:6.2.5] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.39.jar:6.0] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.2.5.jar:6.2.5] at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.39.jar:6.0] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.39.jar:10.1.39] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.2.5.jar:6.2.5] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.5.jar:6.2.5] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.2.5.jar:6.2.5] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.5.jar:6.2.5] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.2.5.jar:6.2.5] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.5.jar:6.2.5] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.39.jar:10.1.39] at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]
I thought maybe it is a thing with Hibernate 5 which I need to use for the session factory for some legacy compatibility but I tried downgrading spring and still get errors.
Originally posted by @btpog in #34733
Comment From: mdeinum
The problem is the fact that with a proxy sessionfactory.openSession()
is called 2 times. Leading to the transaction being run on a different Session
then hibernate has.
This has to do with the logic in the TransactionSyncronizationManager.getResource
this is called twice (once from the HibernateTransactionManager
with the proxied SessionFactory
, while the SpringCurrentSession
calls this method with the actual SessionFactory
.
Doing a small modification in the HibernateTransactionManager
did fix this. When doing the getResource
unwrapping the proxy makes the code succeed as now both use the same key for the getResource
call.
Comment From: btpog
The problem is the fact that with a proxy
sessionfactory.openSession()
is called 2 times. Leading to the transaction being run on a differentSession
then hibernate has.This has to do with the logic in the
TransactionSyncronizationManager.getResource
this is called twice (once from theHibernateTransactionManager
with the proxiedSessionFactory
, while theSpringCurrentSession
calls this method with the actualSessionFactory
.Doing a small modification in the
HibernateTransactionManager
did fix this. When doing thegetResource
unwrapping the proxy makes the code succeed as now both use the same key for thegetResource
call.
Thanks for your response. The double call matches with some of the debugging I have been doing tracing with some output statements. For a while I thought I had 2 different sessionfactory beans being called.
This is a bit above my head, but it looks like getReource has a unwrapResourceIfNecessary call already in it.
If I extend HibernateTransactionManager is there a way to fix it in that?
Comment From: mdeinum
If you override the getSessionFactory
method of the HibernateTransactionManager
with something like this is should work.
@Override
public SessionFactory getSessionFactory() {
var sessionFactory = super.getSessionFactory();
if (sessionFactory instanceof Advised advised) {
var targetSource = advised.getTargetSource();
if (targetSource != null) {
return (SessionFactory) targetSource.getTarget();
}
}
return sessionFactory
}
This will unwrap the proxy. There is probably something you can do with the InfrastructureProxy
as well. But maybe @jhoeller has a better solution (or a fix to go into the framework). For now you could use this as a workaround.
Comment From: btpog
If you override the
getSessionFactory
method of theHibernateTransactionManager
with something like this is should work.@Override public SessionFactory getSessionFactory() { var sessionFactory = super.getSessionFactory(); if (sessionFactory instanceof Advised advised) { var targetSource = advised.getTargetSource(); if (targetSource != null) { return (SessionFactory) targetSource.getTarget(); } } return sessionFactory }
This will unwrap the proxy. There is probably something you can do with the
InfrastructureProxy
as well. But maybe @jhoeller has a better solution (or a fix to go into the framework). For now you could use this as a workaround.
I did a rough test and that seems to work. Thanks.
I am shocked that I am the only person to wrap the session factory in a Proxy.
Comment From: anaconda875
Hi @mdeinum I'm also facing this issue. Will it come to main as a fix? I can submit a PR for this
Comment From: jhoeller
I'm afraid that unconditionally unwrapping an AOP proxy is not a proper general solution here. Other code may be using the proxied SessionFactory reference for lookup purposes, e.g. HibernateTemplate
. The problem here is rather that SpringSessionContext
always operates on the target SessionFactory, not allowing the proxied SessionFactory to be specified there. So the only way to match the SpringSessionContext
lookup is to provide the target SessionFactory to HibernateTransactionManager
as well.
What is the proxied SessionFactory actually doing? If it helps to simply unwrap it for HibernateTransactionManager
, it is obviously not decorating any behavior for transaction management purposes. There is an inherent mismatch here with SpringSessionContext
which will always operate on the target SessionFactory. So if neither the transaction manager nor the transactional Session lookup uses it, is it possibly just for routing to a target SessionFactory - without any decoration of actual SessionFactory methods?
A proxy may explicitly implement InfrastructureProxy
to indicate transparency for resource lookups (applying to the transaction manager as well as to resource lookups). Such an InfrastructureProxy
-implementing SessionFactory reference is probably better off built with java.lang.reflect.Proxy
directly rather than with Spring AOP. Anyway, without such an InfrastructureProxy
indicator, it is unclear whether we are supposed to unwrap a resource proxy and therefore bypass its decorated methods.
Alternatively, putting custom unwrap logic into a HibernateTransactionManager
will obviously work as well. All in all, I'm not seeing a general change we can make here.
Last but not least, please be aware that the orm.hibernate5
package is legacy at this point. It is expected to be dropped completely for 7.0 GA as we complete our Hibernate ORM 7.0 baseline effort, with just a few specific Hibernate support classes moving over to orm.jpa
.
Comment From: btpog
@jhoeller
What is the proxied SessionFactory actually doing?
For my deployment we have separate databases for separate customers. This is important for us from a security perspective to keep the data fully segregated. So we have a SessionFactory Proxy which really just contains a collection of SessionFactory objects, one per customer database. Based upon authentication, a database identifier is acquired and each call is then processed through it's appropriate database. When getTarget is called the correct SessionFactory is returned.
As far as I understand the entire point of AOP is to have the ability to proxy without impacting the underlying code. So this should fit the spring way.