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).
Here's my controller 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)); // <--- this is where the transaction could fail
        /*a bunch of stuff after this line*/
    }
}
Here's the interface for it:
@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);
}
My web-context.xml contains:
    <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>
    <tx:annotation-driven transaction-manager="transactionManager"  proxy-target-class="true"/>
    <context:component-scan base-package="ch.oligofunds.oligoworld.web" scoped-proxy="interfaces" />
And my dao-context.xml contains:
    <context:component-scan base-package="ch.oligofunds.oligoworld.dao" scoped-proxy="interfaces" />
    <context:component-scan base-package="ch.oligofunds.oligoworld.security" scoped-proxy="interfaces" />
    <tx:annotation-driven transaction-manager="transactionManager"  proxy-target-class="true"/>
    <context:property-placeholder location="classpath:CopyofoligoWorld-dao.properties"  />      
        <!-- Using Atomikos Transaction Manager -->
        <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init"
            destroy-method="close">
            <property name="forceShutdown" value="true" />
            <property name="startupTransactionService" value="true" />
            <property name="transactionTimeout" value="60" />
        </bean>
        <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp" />
        <!-- Configure the Spring framework to use JTA transactions from Atomikos -->
        <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
            <property name="transactionManager" ref="atomikosTransactionManager" />
            <property name="userTransaction" ref="atomikosUserTransaction" />
            <property name="transactionSynchronizationName" value="SYNCHRONIZATION_ON_ACTUAL_TRANSACTION" />
        </bean>
                <bean name="mysqlDS,springSecurityDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
                    <property name="driverClassName" value="${mysql.connection.driver_class}" />
                    <property name="username" value="${mysql.connection.username}" />
                    <property name="password" value="${mysql.connection.password}" />
                    <property name="url" value="${mysql.connection.url}" />
                    <property name="maxIdle" value="${mysql.minPoolSize}" />
                    <property name="maxActive" value="${mysql.maxPoolSize}" />
                </bean>
Here's the exception that I thought would trigger the rollback:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Column 'name' cannot be null          
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. :)
Any hints? Thanks
 
     
     
    