【问题标题】:@Transactional on controller method not working@Transactional 控制器方法不起作用
【发布时间】:2015-10-07 03:09:01
【问题描述】:

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

有什么提示吗? 谢谢

【问题讨论】:

  • @Transactional 在该方法上没有任何附加值,因为它是一个内部方法调用。 Spring 使用代理并且只调用通过代理传递的对象。此外,您的代码有缺陷,您不应该捕获和吞下异常,因为这会干扰 tx 支持(它依赖事务来确定是否回滚,目前从来没有异常,因此总是尝试提交)。最后,您使用的是 MySQL,请确保您使用的是实际支持事务的表类型(MyISAM 表不支持 tx)。
  • 我怀疑将事务注释放在控制器级别甚至是一个好习惯。我认为它更好地适用于服务水平。看到这个(stackoverflow.com/questions/1079114/…
  • @StevePark 毫无疑问。但是,正如我在问题中所说,关键是它在技术上是可行的,而我在这里试图理解的是它为什么不起作用。确实,把它放在服务层本身并不能解决问题。
  • @M.Deinum 谢谢,这是一个很好的指针。我尝试将事务移动到控制器端点,并且确实有效。然而那很丑:)所以我最终重组了事情,所以事务不再在控制器中了。您能否将您的评论转换为答案,以便我接受?

标签: java spring hibernate transactions controller


【解决方案1】:

使用 proxy-target-class="true",您是在告诉 spring 使用 cglib 来处理代理,但您已经指定了 scoped-proxy="interfaces"。

https://stackoverflow.com/a/15568457/117839

【讨论】:

  • 添加了 proxy-target-class="true" 因为没有它我收到以下错误:请求处理失败;嵌套异常是 java.lang.IllegalStateException:映射的控制器方法类“ch.oligofunds.oligoworld.web.ExcelUploaderImpl”不是实际控制器 bean 实例“com.sun.proxy.$Proxy69”的实例。如果控制器需要代理(例如由于@Transactional),请使用基于类的代理。我试图理解这一点:我不能同时使用两者?
  • 好的,所以我首先尝试删除 web-context.xml(和 dao-context.xml)中的 proxy-target-class="true",只留下 scoped-proxy="interfaces" .如前所述,我收到了上面评论中提到的错误。然后,我将 proxy-target-class="true" 放回去,并通过删除“implements ExcelUploader”和所有@Override 注释来修改控制器(所以现在根本没有使用该接口)。事情在这里运行,但事务仍然无法正常工作(当第三个插入失败时,前两个插入仍然显示在数据库中)。
【解决方案2】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-06
    • 1970-01-01
    • 1970-01-01
    • 2020-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多