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

Mar*_*nik 5 java spring transactions spring-mvc 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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

控制器类的一个片段:

@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();
    ...
  }
}
Run Code Online (Sandbox Code Playgroud)

这是我的 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是引用的根配置:

package org.example.config;

@Configuration
@ComponentScan
public class RootConfig
{
}
Run Code Online (Sandbox Code Playgroud)

它与这些在同一个包中,由默认组件扫描范围拾取:

@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;
  ...
}
Run Code Online (Sandbox Code Playgroud)

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

我也尝试将userLoggedIn方法提取到 an @Autowired @Transactional @Component,但结果是相同的。

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

我在 Spring 4.0.5。

更新 1

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

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

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

    @Override
    protected Class<?>[] getServletConfigClasses() { return null; }
Run Code Online (Sandbox Code Playgroud)

这直接与文档背道而驰:may not be empty or null. 如果我做相反的事情,给nullforrootConfigClassesRootConfigfor servletConfigClasses,那么一切都会更加失败,“找不到 servlet 上下文。”。

更新 2

失败没有根应用方面存在的已追踪到的Spring Web安全,这显然必须在根级别配置,以由被拾起SecurityWebApplicationInitializer,因为这似乎当根应用程序上下文已经存在的阶段执行,但不是网络应用程序上下文。所以我的问题解决方案是在 root 和 webapp 上下文之间引入分离,其中 root 加载安全性和 webapp 其他所有内容。

Sot*_*lis 4

如果您还没有阅读过它们

这同样适用于AbstractAnnotationConfigDispatcherServletInitializergetRootConfigClasses()getServletConfigClasses()基本上,这WebApplicationInitializer将构造(并注册) aContextLoaderListenerAnnotationConfigWebApplicationContext注册 中的所有@Configuration(和其他@Component带注释的)类getRootConfigClasses()。然后它将构造并注册aDispatcherServlet与.@ConfigurationgetServletConfigClasses()

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

然后容器将初始化注册的DispatcherServlet. 这里还有一些阅读内容

基本上,它通过首先将其父级设置为(由 设置)(如果有的话)来接收的DispatcherServlet意志。refreshApplicationConfigWebApplicationContextApplicationContextServletContextContextLoaderListener

然后,它将开始从中挑选 bean,ApplicationContext以设置 MVC 堆栈、控制器、处理程序方法、拦截器等。默认情况下,它只会在加载的文件中查找其处理程序 bean、@Controllerbean ApplicationContext,而不是其父级 bean。 )。


你似乎做了什么

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

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

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

然后将DispatcherServlet加载WebMvcConfig它自己的@ComponentScanbean,这将创建新的@Controllerbean,但不会建议这些bean,因为没有TransactionInterceptor注册(@EnableTransactionManagement在本上下文中没有注册)。然后,它将DispatcherServlet尝试在其自己的 .xml 文件中查找所有@Controllerbean(以及其他具有@RequestMapping方法的 bean)ApplicationContext。它会找到这些@Controller不建议的豆子。这些是它将注册为处理程序的那些,而不是由ContextLoaderListener.

如果您进一步查看日志,您应该会看到正在创建一个新的控制器 bean。


建议:

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

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

现在我显然不知道您的整个应用程序,但我建议您将所有事务逻辑从处理程序方法重构为某些@Service方法。它将使维护您的配置变得更容易,并使您的控制器更加控制器化,即。委托给模型。