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.
Update 1
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.".
Update 2
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.
You are doing something wrong: both
RootConfig
andWebMvcConfig
are in the same package.RootConfig
does component scanning in its own package, discoversWebMvcConfig
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 inWebApplicationInitializer
) 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
andWebMvcConfig
in separate packages.If you haven't already read them
The same applies to
AbstractAnnotationConfigDispatcherServletInitializer
'sgetRootConfigClasses()
andgetServletConfigClasses()
. Basically thatWebApplicationInitializer
will construct (and register) aContextLoaderListener
with aAnnotationConfigWebApplicationContext
registering all the@Configuration
(and other@Component
annotated) classes fromgetRootConfigClasses()
. It will then construct and register aDispatcherServlet
with all the@Configuration
(and other...) classes from thegetServletConfigClasses()
.As part of the Servlet lifecycle, the container will first initialize all
ServletContextListener
objects. This meansContextLoaderListener
will load first andrefresh
theAnnotationConfigWebApplicationContext
that was given to it (if it wasn't already refreshed, which ideally it shouldn't be). It will also put thisApplicationContext
as an attribute in theServletContext
.The container will then initialize the registered
DispatcherServlet
. Here's some more readingBasically, the
DispatcherServlet
willrefresh
theApplicationConfigWebApplicationContext
that it received by first setting its parent to theApplicationContext
in theServletContext
(set by theContextLoaderListener
), 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 theApplicationContext
it loaded, not its parent(s).What you seem to have done is
and
In this case, the
ContextLoaderListener
will loadRootConfig
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 loadWebMvcConfig
which has its own@ComponentScan
and this will create new@Controller
beans, but these won't be advised because noTransactionInterceptor
was registered (no@EnableTransactionManagement
in this context). TheDispatcherServlet
will then try to find all@Controller
beans (and other beans that have@RequestMapping
methods) in its ownApplicationContext
. It will find these@Controller
beans which aren't advised. Those are the ones it will register as handlers, not the ones loaded by theContextLoaderListener
.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.