我有一个Spring MVC Controller方法,它被标记为"Transactional",它进行了几个服务调用,这些调用也被标记为"Transactional",但它们被视为独立的事务,并且被单独提交,而不是按我的意愿全部提交到一个事务下。
根据调试输出,Spring似乎只是在到达服务调用时才创建事务。我将只看到它们的输出"正在创建名为…的新事务",而不会看到调用它们的控制器方法的输出。
有什么明显的东西我遗漏了吗?
示例代码(缩写)如下,控制器:
@Controller
public class BlahBlah etc...
@Autowired
SomeService someService;
@Transactional
@RequestMapping(value="windows/submit", method=RequestMethod.POST)
@ResponseBody
public String submit (@RequestBody SomeObject blah) throws Exception {
someService.doInsert(blah);
if (true) throw Exception("blah");
... other stuff
}
服务代码:
public interface SomeService {
@Transactional
public void doInsert (SomeObject blah);
}
我试图从服务调用中删除Transactional标记,认为可能我把它搞砸了,我告诉它为每个调用创建一个,但在调试输出中没有创建任何事务。
因此,一旦我得到强制异常并检查表,插入就已经提交,而不是像我想要的那样回滚。
那么我做错了什么?
为什么Spring忽略控制器上的Transactional标记?
根据评论者的请求发布我的上下文的相关部分:
Web.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring.xml
classpath:spring-security.xml
classpath:spring-datasource.xml
</param-value>
</context-param>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
sping-mvc.xml:
<context:annotation-config/>
<context:component-scan base-package="com.blah"/>
<mvc:annotation-driven/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver" p:order="1" />
... non-relevent stuff
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="webContentInterceptor" class="org.springframework.web.servlet.mvc.WebContentInterceptor">
<property name="cacheSeconds" value="0"/>
<property name="useExpiresHeader" value="true"/>
<property name="useCacheControlHeader" value="true"/>
<property name="useCacheControlNoStore" value="true"/>
</bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="httpInterceptor" class="com.blah.BlahInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
<mvc:view-controller path="/" view-name="blah"/>
<context:component-scan base-package="com.blah"/>
嗯,这是感兴趣的,也是无意的——我有重复的组件扫描。这会造成问题吗?
我认为Spring忽略了这里的@Transactional注释,因为它为事务创建了一个代理,但调度器并没有通过代理调用控制器。
Spring MVC文档17.3.2中有一个关于注释控制器的有趣注释,它没有描述您的确切问题,但表明这种方法存在问题:
使用带注释的控制器类时的常见陷阱在应用需要为创建代理的功能时发生控制器对象(例如@Transactional方法)。通常你会为控制器引入一个接口,以便使用JDK动态代理。要实现此操作,您必须移动@RequestMapping注释,以及任何其他类型和方法级别的注释(例如@ModelAttribute、@InitBinder)添加到接口以及映射机制只能"查看"代理公开的接口。或者,您可以在应用于控制器的功能的配置(在中的事务场景)。这样做表明应该使用基于CGLIB的子类代理,而不是基于接口的JDK代理。有关各种代理的更多信息机制见第9.6节"代理机制"。
我认为您最好创建另一个服务来包装现有的服务并对其进行注释。