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

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

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.

Comment From: jhoeller

@btpog ah ok, that's what I assumed. So in your case it is irrelevant what the rest of the SessionFactory methods would be doing since you know that you are delegating everything straight to a particular target SessionFactory. No further additional behavior added, so it can safely be bypassed for any further purposes. You should express this to the resource handling infrastructure.

Try implementing InfrastructureProxy in your SessionFactory proxy. As long as getWrappedObject() returns the actual target SessionFactory, resource binding should work automatically then. You can do this with a mixin in Spring AOP but it is even more straightforward (and efficient) with java.lang.reflect.Proxy.

Take a look at BootstrapSessionFactoryInvocationHandler in LocalSessionFactoryBuilder for inspiration. A rough sketch:

    class DelegatingSessionFactoryInvocationHandler implements InvocationHandler {

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return switch (method.getName()) {
                case "equals" -> (proxy == args[0]);
                case "hashCode" -> System.identityHashCode(proxy);
                case "getWrappedObject" -> getTargetSessionFactory();
                default -> {
                    try {
                        yield method.invoke(getTargetSessionFactory(), args);
                    }
                    catch (InvocationTargetException ex) {
                        throw ex.getTargetException();
                    }
                }
            };
        }
    }

With a proxy to be created as follows, for example in a custom FactoryBean:

    Proxy.newProxyInstance(getClass().getClassLoader(),
            new Class<?>[] {SessionFactoryImplementor.class, InfrastructureProxy.class},
            new DelegatingSessionFactoryInvocationHandler());

Comment From: mdeinum

@jhoeller just wondering if it would make sense if a bean annotated with @Role(BeanDefinition.ROLE_INFRASTRUCTURE) or otherwise marked as used for that role (xml, or modifying the BeanDefinition) to automatically have the InfrastructureProxy interface (and lookup) applied?

Comment From: btpog

Thanks so much. I am struggling a bit following all this, but I think I get the gist that certain mechanisms higher up the spring food chain override some of the AOP behavior and thus have to be manually implemented or alternatively it can be explicitly called out by using this InfrastructureProxy.

It took me a little bit of fooling around to sort out how to get InfrastructureProxy implementation style working in my code. Your examples work good, my struggle isn't implementing the interface so much as plumbing it. THis is likely because I have some frankenstein code here as I am trying to migrate this old code base to new spring.
I do have some spring 5 in here but I think what confused me is the new proxy style and mixing it into my xml config. I am using xml config for the data config. As I described I have a large number of databases we are referencing and managing this in xml config always seemed more appropriate. I was implementing the SessionFactory proxy using a ProxyFactoryBean in xml config. I couldn't figure out how to do that with the method you were suggesting. So I had to mix my xml with annotations. Not really liking how that looks, but it works.

Your original suggestion of override the getSessionFactory method of the HibernateTransactionManager looks a little cleaner in the code. I am guessing the InfrastructureProxy is more appropriate though. I can't decide which way to go.