控制器方法上的@Transactional 不起作用

@Transactional on controller method not working

在我的 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);
}

我的网站-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 注释的。但我的理解可能是错误的。 :)

有什么提示吗? 谢谢

使用 proxy-target-class="true" 你告诉 spring 使用 cglib 来处理代理,但你指定了 scoped-proxy="interfaces".

该方法的

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

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

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

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