【发布时间】: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