[英]@Transactional on controller method not working
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. 在我的Spring MVC应用程序中,我在控制器中有一个方法,该方法需要将一堆对象(从上载的文件构建)保存到数据库中。 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.
如果您看下面的代码,我期望的是,如果对saveContact的三个调用中的任何一个失败并带有一个异常(任何异常,因为我放了rollbackFor = Exception.class),那么这三个都应该回滚。 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).
引发的异常是PersistenceException,因此我认为这应该触发回滚,但不会触发(回滚到客户端的浏览器,这是我期望的,因为我没有捕获它)。
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: 我的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" />
And my dao-context.xml contains: 我的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>
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. 对我来说,目前尚不清楚是否@Transactional批注已被拾取-从我的理解来看,应该如此,因为我正在扫描ch.oligofunds.oligoworld.web软件包,并且控制器接口已使用@Controller进行批注。 But my understanding may be wrong.
但是我的理解可能是错误的。 :)
:)
Any hints? 有什么提示吗? Thanks
谢谢
With proxy-target-class="true" you're telling spring to use cglib to handle the proxying but you've specified scoped-proxy="interfaces". 使用proxy-target-class =“ true”告诉Spring使用cglib来处理代理,但是您指定了scoped-proxy =“ interfaces”。
See https://stackoverflow.com/a/15568457/117839 参见https://stackoverflow.com/a/15568457/117839
@Transactional
on that method doesn't have any added value as it is an internal method call (and your class is already transactional). 该方法上的
@Transactional
没有任何附加值,因为它是内部方法调用(并且您的类已经是事务性的)。 Spring uses proxies and only calls into the object pass thorough the proxy. Spring使用代理,只有对对象的调用才能通过代理传递。
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). 同样,您的代码也存在缺陷,您不应捕获和吞下异常,因为这会干扰对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). 最后,您使用MySQL来确保您使用的表类型实际上支持事务(MyISAM表不支持tx)。
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.
控制器(通常是Web层)应该只是一个薄层,它将传入的请求转换为可用于服务层的内容,并将结果转换为可用于Web显示的内容。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.