title: @Transactional on controller method not working
body: <p>In my Spring MVC application, I have a method in a controller that needs to save a bunch of objects (built from an uploaded file) to a database. Let us leave aside for the moment the whole question about whether transactions should be done in the controller or service layer -- the point is that it should technically be feasible to do it in the controller, but I am finding problems.
  If you look at the code below, what I am expecting is that if any of the three calls to saveContact fails with an Exception (any exception, since I put rollbackFor = Exception.class ), then all three should be rolled back. Still, what I see is that if for example the third one fails, the data from the first two is still present in the database. The exception thrown is a PersistenceException, so I believe this should trigger the rollback, but it doesn't (it bubbles up to the client's browser, which is what I expected since I'm not catching it).</p>
  
  <p>Here's my controller code:</p>
  
  <pre><code>package ch.oligofunds.oligoworld.web;
  
  /*imports here*/
  
  /**
   * Handles requests for the application file upload requests
   */
  @Controller("ExcelUploaderImpl")
  @Transactional
  public class ExcelUploaderImpl implements ExcelUploader {
  
      @Autowired
      PersoninfoDAO personinfoDAO;
  
      /**
       * Upload files using Spring Controller
       * @throws SecurityException 
       * @throws NoSuchMethodException 
       * @throws DataAccessException 
       */
      @Override
      @RequestMapping(value = "/importFundNAV", method = RequestMethod.POST)
      public @ResponseBody String handleFileUpload(HttpServletRequest request, @RequestParam CommonsMultipartFile[] fileUpload) throws DataAccessException, NoSuchMethodException, SecurityException {
  
                  /*here save the uploaded file and initialize the serverFile variable*/
  
  
                  try {
                      success = readExcelfile(serverFile);
                  } catch (IOException e) {
                      logger.error("Failed to read the excel file", e);
                      result += "Failed to read the excel file\n" + e.getStackTrace() + "\n";
                  } finally {
                      serverFile.delete();
                  }
                  if (success) {
                      result += "You successfully imported file " + aFile.getOriginalFilename() + "\n";
                  } else {
                      result += "Failed to import file " + aFile.getOriginalFilename() + "\n";
                  }
              }
              return result;
  
      }
  
      @Override
      public boolean readExcelfile(File xlfile) throws IOException, DataAccessException, NoSuchMethodException, SecurityException {
          FileInputStream fis = new FileInputStream(xlfile); // Finds the workbook
                                                              // instance for XLSX
                                                              // file
          XSSFWorkbook myWorkBook = new XSSFWorkbook(fis); // Return first sheet
                                                              // from the XLSX
                                                              // workbook
          boolean success;
          success = readFundDefinition(myWorkBook);
          myWorkBook.close();
          return success;
      }
  
      @Override
      @Transactional(rollbackFor = Exception.class)
      public boolean readFundDefinition(XSSFWorkbook myWorkBook) throws DataAccessException, NoSuchMethodException, SecurityException {
  
              /*here do stuff to extract data from the excel to initialize the administrator, custodian, invContact and success variables*/
  
              saveContact(administrator);
              saveContact(custodian);
              saveContact(invContact);
              /*If any of the three invocations to saveContact fails, I want all three inserts to rollback*/
  
  
          return success;
      }
  
      @Override
      public void saveContact(Personinfo personinfo) throws DataAccessException, NoSuchMethodException, SecurityException {
          /*a bunch of stuff before this line*/
                  personinfo.copy(personinfoDAO.store(personinfo)); // &lt;--- this is where the transaction could fail
          /*a bunch of stuff after this line*/
      }
  }
  </code></pre>
  
  <p>Here's the interface for it:</p>
  
  <pre><code>@Controller
  public interface ExcelUploader {
  
      public @ResponseBody String handleFileUpload(HttpServletRequest request, @RequestParam CommonsMultipartFile[] fileUpload) throws DataAccessException, NoSuchMethodException, SecurityException;
  
      public boolean readExcelfile(File xlfile) throws IOException, DataAccessException, NoSuchMethodException, SecurityException;
  
      public boolean readFundDefinition(XSSFWorkbook myWorkBook) throws DataAccessException, NoSuchMethodException, SecurityException;
  
      public void saveContact(Personinfo personinfo, Personaddress personAddress, Personemail personEmail, Personphone personPhone) throws DataAccessException, NoSuchMethodException, SecurityException;
  
      public void readNAV(XSSFWorkbook myWorkBook);
  
      public boolean isEmpty(Object object, Method... methods);
  }
  </code></pre>
  
  <p>My web-context.xml contains:</p>
  
  <pre><code>    &lt;mvc:annotation-driven/&gt;
  
      &lt;mvc:default-servlet-handler/&gt;
  
      &lt;tx:annotation-driven transaction-manager="transactionManager"  proxy-target-class="true"/&gt;
  
      &lt;context:component-scan base-package="ch.oligofunds.oligoworld.web" scoped-proxy="interfaces" /&gt;
  </code></pre>
  
  <p>And my dao-context.xml contains:</p>
  
  <pre><code>    &lt;context:component-scan base-package="ch.oligofunds.oligoworld.dao" scoped-proxy="interfaces" /&gt;
      &lt;context:component-scan base-package="ch.oligofunds.oligoworld.security" scoped-proxy="interfaces" /&gt;
  
      &lt;tx:annotation-driven transaction-manager="transactionManager"  proxy-target-class="true"/&gt;
  
  
      &lt;context:property-placeholder location="classpath:CopyofoligoWorld-dao.properties"  /&gt;      
  
  
          &lt;!-- Using Atomikos Transaction Manager --&gt;
          &lt;bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init"
              destroy-method="close"&gt;
              &lt;property name="forceShutdown" value="true" /&gt;
              &lt;property name="startupTransactionService" value="true" /&gt;
              &lt;property name="transactionTimeout" value="60" /&gt;
          &lt;/bean&gt;
  
          &lt;bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp" /&gt;
  
          &lt;!-- Configure the Spring framework to use JTA transactions from Atomikos --&gt;
          &lt;bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"&gt;
              &lt;property name="transactionManager" ref="atomikosTransactionManager" /&gt;
              &lt;property name="userTransaction" ref="atomikosUserTransaction" /&gt;
              &lt;property name="transactionSynchronizationName" value="SYNCHRONIZATION_ON_ACTUAL_TRANSACTION" /&gt;
          &lt;/bean&gt;
  
  
                  &lt;bean name="mysqlDS,springSecurityDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" &gt;
                      &lt;property name="driverClassName" value="${mysql.connection.driver_class}" /&gt;
                      &lt;property name="username" value="${mysql.connection.username}" /&gt;
                      &lt;property name="password" value="${mysql.connection.password}" /&gt;
                      &lt;property name="url" value="${mysql.connection.url}" /&gt;
                      &lt;property name="maxIdle" value="${mysql.minPoolSize}" /&gt;
                      &lt;property name="maxActive" value="${mysql.maxPoolSize}" /&gt;
                  &lt;/bean&gt;
  </code></pre>
  
  <p>Here's the exception that I thought would trigger the rollback:</p>
  
  <pre><code>org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Column 'name' cannot be null          
  </code></pre>
  
  <p>It's not clear to me whether the @Transactional annotation is being picked up at all - from my understanding it should, since I'm scanning the ch.oligofunds.oligoworld.web package and the controller interface is annotated with @Controller. But my understanding may be wrong. :)</p>
  
  <p>Any hints?
  Thanks</p>