I have a @Transactional @Controller
, but its methods are being invoked by the Spring MVC framework without a transaction. In the exception trace I do not find the transaction advisor intercepting the call:
org.hibernate.HibernateException: No Session found for current thread
org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:106)
org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
org.example.businesslogic.MyController.userLoggedIn(SwiperRest.java:48)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:483)
org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:689)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938)
On the other hand, the log clearly indicates that the controller methods were detected as transactional:
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'org.springframework.transaction.config.internalTransactionAdvisor'
DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'metaDataSourceAdvisor'
DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'MyController.userLoggedIn' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
DEBUG o.s.a.f.a.InfrastructureAdvisorAutoProxyCreator - Creating implicit proxy for bean 'myController' with 0 common interceptors and 1 specific interceptors
DEBUG o.s.a.f.CglibAopProxy - Creating CGLIB proxy: target source is SingletonTargetSource for target object [org.example.businesslogic.MyController@7c0f1b7c]
DEBUG o.s.a.f.CglibAopProxy - Unable to apply any optimisations to advised method: public java.lang.String org.example.businesslogic.MyController.userLoggedIn(java.lang.String,java.lang.String)
DEBUG o.s.t.a.AnnotationTransactionAttributeSource - Adding transactional method 'MyController.locationProfiles' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
DEBUG o.s.a.f.CglibAopProxy - Unable to apply any optimisations to advised method: public java.util.List org.example.businesslogic.MyController.locationProfiles(java.lang.String)
A snippet from the controller class:
@Transactional
@Controller
@RequestMapping("/zendor")
public class MyController
{
@Autowired private SessionFactory sf;
@RequestMapping(method=POST, value="userLoggedIn")
public @ResponseBody String userLoggedIn(@RequestParam String u_id, @RequestParam String d_id) {
Session hb = sf.getCurrentSession();
...
}
}
This is my web application initializer class, I don't have a web.xml
:
public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
@Override
protected Class<?>[] getRootConfigClasses() { return new Class[] { RootConfig.class }; }
@Override
protected Class<?>[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; }
@Override
protected String[] getServletMappings() { return new String[] { "/" }; }
@Override public void onStartup(ServletContext ctx) throws ServletException {
ctx.setInitParameter("spring.profiles.active", "production");
super.onStartup(ctx);
}
}
This is the referenced root configuration:
package org.example.config;
@Configuration
@ComponentScan
public class RootConfig
{
}
It is in the same package as these, which get picked up by the default component scan range:
@Configuration
@EnableWebMvc
@ComponentScan("org.example.businesslogic")
public class WebMvcConfig extends WebMvcConfigurationSupport
{
}
@Configuration
@EnableTransactionManagement
@ComponentScan("org.example.businesslogic")
public class DataConfig implements TransactionManagementConfigurer
{
@Autowired private DataSource dataSource;
...
}
When the same configuration is used by Spring-test's SpringJUnit4ClassRunner
, the methods do get advised and transactions work.
I also tried to extract the userLoggedIn
method to an @Autowired
@Transactional @Component
, but the result was identical.
In which direction should I inveltigate to resolve this issue?
I am on Spring 4.0.5.
The key problem is that my root config is pulling in all other config classes as well, including WebMvcConfig
, which is loaded again as the child servlet config.
Quite counterintuitively, things only start working when I remove the servlet config class, replacing
@Override
protected Class<?>[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; }
with
@Override
protected Class<?>[] getServletConfigClasses() { return null; }
which goes directly against the documentation: may not be empty or null
. If I do the reverse, giving null
for rootConfigClasses
and RootConfig
for servletConfigClasses
, then everything fails even harder, with "servlet context not found.".
The failure occuring without root app context has been traced to Spring Web Security, which must apparently be configured at the root level in order to be picked up by the SecurityWebApplicationInitializer
, as this seems to executed at a stage when the root app context already exists, but not the web app context. So my problem resolution was to introduce a separation between root and webapp contexts, where the root loads security and webapp everything else.
If you haven't already read them
The same applies to AbstractAnnotationConfigDispatcherServletInitializer
's getRootConfigClasses()
and getServletConfigClasses()
. Basically that WebApplicationInitializer
will construct (and register) a ContextLoaderListener
with a AnnotationConfigWebApplicationContext
registering all the @Configuration
(and other @Component
annotated) classes from getRootConfigClasses()
. It will then construct and register a DispatcherServlet
with all the @Configuration
(and other...) classes from the getServletConfigClasses()
.
As part of the Servlet lifecycle, the container will first initialize all ServletContextListener
objects. This means ContextLoaderListener
will load first and refresh
the AnnotationConfigWebApplicationContext
that was given to it (if it wasn't already refreshed, which ideally it shouldn't be). It will also put this ApplicationContext
as an attribute in the ServletContext
.
The container will then initialize the registered DispatcherServlet
. Here's some more reading
Basically, the DispatcherServlet
will refresh
the ApplicationConfigWebApplicationContext
that it received by first setting its parent to the ApplicationContext
in the ServletContext
(set by the ContextLoaderListener
), if there is one.
It will then start picking and choosing beans from its ApplicationContext
to set up the MVC stack, controllers, handler methods, interceptors, etc. By default, it will only look up its handler beans, @Controller
beans, in the ApplicationContext
it loaded, not its parent(s).
What you seem to have done is
@Override
protected Class<?>[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; }
and
@Override
protected Class<?>[] getRootConfigClasses() { return new Class[] { RootConfig.class }; }
In this case, the ContextLoaderListener
will load RootConfig
which will create a bunch of beans, including ones for your @Controller
classes which will be advised with the @Transactional
configuration.
The DispatcherServlet
will then load WebMvcConfig
which has its own @ComponentScan
and this will create new @Controller
beans, but these won't be advised because no TransactionInterceptor
was registered (no @EnableTransactionManagement
in this context). The DispatcherServlet
will then try to find all @Controller
beans (and other beans that have @RequestMapping
methods) in its own ApplicationContext
. It will find these @Controller
beans which aren't advised. Those are the ones it will register as handlers, not the ones loaded by the ContextLoaderListener
.
If you look further down in your logs, you should see a new controller bean(s) being created.
Suggestions:
Controllers are not components that the whole application should have access to. Only the DispatcherServlet
should care about them. Put them in the servlet context.
Now I obviously don't know your whole application, but I recommend you refactor all the transactional logic out of the handler methods and into some @Service
methods. It will make it easier to maintain your configs and make your controllers more controller-y, ie. delegate to the model.
You are doing something wrong: both RootConfig
and WebMvcConfig
are in the same package. RootConfig
does component scanning in its own package, discovers WebMvcConfig
which in turn does component scanning. In the end the root application context will contain all transactional related stuff (txManager, datasource, sessionfactorybean etc) but, also, everything web related: controllers, handlermappings etc.
Then, WebMvcConfig
kicks in (because it's defined in WebApplicationInitializer
) and all web-related stuff is re-defined again. And I think it's happening the way it does, because the root context has one version of your controller (the transactional one) and the servlet context has another version (the simple one).
I think you need to keep your RootConfig
and WebMvcConfig
in separate packages.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With