即使在日志中看到“添加事务方法”,方法也不被事务顾问拦截

时间:2014-06-23 13:41:03

标签: java spring spring-mvc transactions spring-jdbc

我有一个@Transactional @Controller,但它的方法是由Spring MVC框架调用的,没有事务。在异常跟踪中,我没有找到拦截调用的事务顾问:

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)

另一方面,日志清楚地表明控制器方法被检测为事务性的:

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)

控制器类的片段:

@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();
    ...
  }
}

这是我的Web应用程序初始化程序类,我没有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);
    }
}

这是引用的根配置:

package org.example.config;

@Configuration
@ComponentScan
public class RootConfig
{
}

它与这些包在同一个包中,可以通过默认的组件扫描范围获取:

@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;
  ...
}

当Spring-test的SpringJUnit4ClassRunner使用相同的配置时,方法会得到建议并且事务可以正常工作。

我还尝试将userLoggedIn方法提取到@Autowired @Transactional @Component,但结果完全相同。

我应该以哪个方向来解决这个问题?

我在Spring 4.0.5上。

更新1

关键问题是我的root配置也包括所有其他配置类,包括WebMvcConfig,它再次作为子servlet配置加载。

相反违反直觉,当我删除 servlet配置类时,事情才开始工作,替换

    @Override
    protected Class<?>[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; }

    @Override
    protected Class<?>[] getServletConfigClasses() { return null; }

直接针对文档:may not be empty or null。如果我执行相反的操作,为null提供rootConfigClasses,为RootConfig提供servletConfigClasses,那么一切都会失败,因为“找不到servlet上下文。”。

更新2

没有root应用程序上下文时发生的故障已追溯到Spring Web Security,它必须显然在根级别配置才能被SecurityWebApplicationInitializer选中,因为这似乎是在某个阶段执行的根应用程序上下文已存在,但不存在Web应用程序上下文。所以我的问题解决方案是在root和webapp上下文之间引入分离,其中root将加载安全性和webapp所有其他内容。

2 个答案:

答案 0 :(得分:3)

如果你还没有读过它们

这同样适用于AbstractAnnotationConfigDispatcherServletInitializer的{​​{1}}和getRootConfigClasses()。基本上getServletConfigClasses()WebApplicationInitializer构建(并注册)ContextLoaderListenerAnnotationConfigWebApplicationContext注册来自{{1}的所有@Configuration(以及其他@Component注释的)类}。然后,它将构建并注册getRootConfigClasses(),其中包含DispatcherServlet中的所有@Configuration(和其他...)类。

作为Servlet生命周期的一部分,容器将首先初始化所有getServletConfigClasses()个对象。这意味着ServletContextListener将首先加载ContextLoaderListener refresh加载给它的AnnotationConfigWebApplicationContext(如果它尚未刷新,理想情况下它不应该被刷新)。它还会将此ApplicationContext作为属性放在ServletContext

然后容器将初始化已注册的DispatcherServlet。这是一些更多的阅读

基本上,DispatcherServlet refreshApplicationConfigWebApplicationContext收到的ApplicationContext首先将其父级设置为ServletContext中的ContextLoaderListener(由{设置} {1}}),如果有的话。

然后它将从ApplicationContext开始挑选并选择bean来设置MVC堆栈,控制器,处理程序方法,拦截器等。默认情况下,它只会查找它处理程序bean,@Controller bean,在它加载的ApplicationContext中,而不是它的父项。


你似乎做的是

@Override
protected Class<?>[] getServletConfigClasses() { return new Class[] { WebMvcConfig.class }; }

@Override
protected Class<?>[] getRootConfigClasses() { return new Class[] { RootConfig.class }; }

在这种情况下,ContextLoaderListener将加载RootConfig,这将创建一堆bean,包括@Controller类的bean,将通过@Transactional配置建议

DispatcherServlet然后会加载WebMvcConfig,它有自己的@ComponentScan,这将创建新的@Controller bean,但不会建议这些因为没有{{1}已注册(在此上下文中没有TransactionInterceptor)。然后,@EnableTransactionManagement会尝试在自己的DispatcherServlet中找到所有@Controller bean(以及其他具有@RequestMapping方法的bean)。它会找到不建议的这些ApplicationContext bean。这些是它将注册为处理程序的那些,而不是由@Controller加载的那些。

如果你在日志中向下看,你会看到正在创建一个新的控制器bean。


建议:

  • 根上下文:整个应用程序应该可见的内容
  • Servlet上下文:MVC堆栈应该可见的内容

控制器不是整个应用程序应该有权访问的组件。只有ContextLoaderListener应该关心他们。将它们放在servlet上下文中。

现在我显然不知道你的整个应用程序,但我建议你从处理程序方法和一些DispatcherServlet方法中重构所有事务逻辑。这将使您更容易维护您的配置,并使您的控制器更多控制器,即。委托给模特。

答案 1 :(得分:1)

您做错了:RootConfigWebMvcConfig都在同一个包中。 RootConfig在自己的包中进行组件扫描,发现WebMvcConfig进而进行组件扫描。最后,根应用程序上下文将包含所有与事务相关的内容(txManager,datasource,sessionfactorybean等),但也包含与Web相关的所有内容:controllers,handlermappings等。

然后,WebMvcConfig开始(因为它在WebApplicationInitializer中定义)并且所有与网络相关的内容再次被重新定义。我认为它正在发生它的方式,因为根上下文有一个版本的控制器(事务性的)和servlet上下文有另一个版本(简单的版本)。

我认为您需要将RootConfigWebMvcConfig放在不同的包中。