@Transactional控制器方法不起作用

时间:2015-07-17 11:45:26

标签: java spring hibernate transactions controller

在我的Spring MVC应用程序中,我在控制器中有一个方法需要将一堆对象(从上传的文件构建)保存到数据库中。让我们暂时搁置关于交易是否应该在控制器或服务层完成的整个问题 - 关键是在技术上应该在控制器中完成它,但我发现问题。 如果你看下面的代码,我期待的是,如果对saveContact的三次调用中的任何一次失败都带有异常(任何异常,因为我放了rollbackFor = Exception.class),那么所有三个都应该回滚。但是,我看到的是,例如,如果第三个失败,前两个数据仍然存在于数据库中。引发的异常是PersistenceException,所以我认为这应该会触发回滚,但它不会(它会冒泡到客户端的浏览器,这是我所期望的,因为我没有捕获它)。

这是我的控制器代码:

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*/
    }
}

以下是它的界面:

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

我的web-context.xml包含:

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

我的dao-context.xml包含:

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

以下是我认为会触发回滚的例外情况:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Column 'name' cannot be null          

我不清楚@Transactional注释是否被拾取 - 从我的理解应该是,因为我正在扫描ch.oligofunds.oligoworld.web包并且控制器接口用@Controller注释。但我的理解可能是错的。 :)

任何提示? 感谢

2 个答案:

答案 0 :(得分:2)

使用proxy-target-class =&#34; true&#34;你告诉spring使用cglib来处理代理,但是你已经指定了scoped-proxy =&#34; interfaces&#34;。

请参阅 https://stackoverflow.com/a/15568457/117839

答案 1 :(得分:0)

该方法的

@Transactional没有任何附加值,因为它是一个内部方法调用(并且您的类已经是事务性的)。 Spring使用代理,只有通过代理才能调用对象。

此外,您的代码存在缺陷,您不应该捕获并吞下异常,因为这会干扰tx支持(它依赖于事务来确定是否回滚,目前从来没有异常因此总是尝试提交)。

最后你使用MySQL确保使用实际支持事务的表类型(MyISAM表没有tx支持)。

但我强烈建议将事务部分(或您正在控制器中执行的业务逻辑)移动到服务。控制器(或一般的Web层)应该只是一个薄层,将传入的请求转换为可用于服务层的内容,并将结果转换为可用于Web显示的内容。