Transaction not working correctly - Spring/MyBatis

2019-05-27 10:48发布

问题:

I have a Spring MVC Controller method which is tagged as "Transactional" which makes several service calls which are also tagged "Transactional" but they are treated as independent transactions and are committed separately instead of all under one transaction as I desire.

Based on the debug output, it appears Spring is only creating transactions when it reaches the service calls. I will see the output "Creating new transaction with name..." only for them but not one for the controller method who calls them.

Is there something obvious I am missing?

Sample code (abbreviated) below, controller:

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

service code:

public interface SomeService {

@Transactional
public void doInsert (SomeObject blah);
}

I tried removing the Transactional tag from the service call thinking maybe I messed it up and I am telling it to create one for each call but then in the debug output no transaction is created.

So the result is once I get my forced exception and check the table, the insert has committed instead of rolled back like I want.

So what did I do wrong?

Why is Spring ignoring the Transactional tag on the controller?


Posting relevant part of my context as per request from commenter:

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"/> 

Hmm, thats interested and unintended - I have component scan duplicated. Could that be causing problems with this?

回答1:

I think Spring ignores the @Transactional annotation here because it creates a proxy for the transaction, but the dispatcher isn't calling the controller through the proxy.

There's an interesting note in the Spring MVC documentation, 17.3.2, about annotating controllers, it doesn't describe your exact problem but shows that there are problems with this approach:

A common pitfall when working with annotated controller classes happens when applying functionality that requires creating a proxy for the controller object (e.g. @Transactional methods). Usually you will introduce an interface for the controller in order to use JDK dynamic proxies. To make this work you must move the @RequestMapping annotations, as well as any other type and method-level annotations (e.g. @ModelAttribute, @InitBinder) to the interface as well as the mapping mechanism can only "see" the interface exposed by the proxy. Alternatively, you could activate proxy-target-class="true" in the configuration for the functionality applied to the controller (in our transaction scenario in ). Doing so indicates that CGLIB-based subclass proxies should be used instead of interface-based JDK proxies. For more information on various proxying mechanisms see Section 9.6, “Proxying mechanisms”.

I think you'd be better off creating another service to wrap the existing ones and annotating that.