@Transactional在控制器方法上不起作用 [英] @Transactional on controller method not working

查看:96
本文介绍了@Transactional在控制器方法上不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

这是我的控制器代码:

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包含:

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

我的dao-context.xml包含:

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          

对于我来说,尚不清楚是否@Transactional注释是否已被拾取-据我所知,因为我正在扫描ch.oligofunds.oligoworld.web软件包,并且控制器接口已使用@Controller进行注释。但是我的理解可能是错误的。 :)

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

推荐答案

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

@Transactional on that method doesn't have any added value as it is an internal method call (and your class is already transactional). Spring uses proxies and only calls into the object pass thorough the proxy.

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

Also your code is flawed you shouldn't catch and swallow exceptions as that interferes with the tx support (it relies on transactions to determine to rollback or not, currently there is never an exception hence always tries to commit).

最后,您使用的是MySQL,请确保您使用的表类型实际上支持事务(MyISAM表不支持tx)。

Finally you are using MySQL make sure that you are using table types that actually supports transactions (MyISAM tables don't have tx support).

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

I would however strongly suggest to move the transactional part (or the business logic which you are now doing in your controller) to a service. The controller (or web layer in general) should only be a thin layer converting the incoming request into something useable for the service layer and the result into something useable for the web to display.

这篇关于@Transactional在控制器方法上不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆