提交表单中有文件上传后台如何保证数据的一致性
- - 企业架构 - ITeye博客在公司开发一个后台管理系统时有这样的需求:提交一个表单时,要把表单域内容和上传的文件内容(可以是多个上传文件)一并提交到后台去,并且数据库持久化失败后数据要回滚且文件不应该上传上去,如果文件上传失败同样数据库也要回滚. Spring MVC的controller只是将参数包装成DTO,提交给service层一并处理文件上传和数据库保存操作.
在公司开发一个后台管理系统时有这样的需求:提交一个表单时,要把表单域内容和上传的文件内容(可以是多个上传文件)一并提交到后台去,并且数据库持久化失败后数据要回滚且文件不应该上传上去,如果文件上传失败同样数据库也要回滚。
我的做法是:
1. Spring MVC的controller只是将参数包装成DTO,提交给service层一并处理文件上传和数据库保存操作。controller中的方法,如:
@RequestMapping("save.do") public @ResponseBody String storySave(MultipartHttpServletRequest request, StoryDTO storyDTO, String[] fileName) throws Exception { storyDTO.setFiles(request.getFileMap()); storyDTO.setFileUploadPath(webConfig.getFileUploadPath()); storyDTO.setFileName(fileName); try { storyService.processingStory(storyDTO); } catch (Exception e) { e.printStackTrace(); return "{\"status\":\"err\"}"; } return "{\"status\":\"ok\"}"; }
在service里的方法processingStory是被Spring事务管控的,那么这个方法头部就抛出一个Exception异常,且在这个方法里的操作一定是数据持久化操作在先,而文件操作在后。
文件操作的潜在的异常也是由processingStory这个方法统一抛出的。service中的处理方法如下:
@Override public void processingStory(StoryDTO storyDTO) throws Exception { Map<String, MultipartFile> files = storyDTO.getFiles(); Boolean file1=null,file2=null,file3=null,file4=null; String[] fileName = storyDTO.getFileName(); String fileUploadPath = storyDTO.getFileUploadPath(); String[] pathNames = new String[4]; InputStream[] ins = new InputStream[4]; for (Entry<String, MultipartFile> entry : files.entrySet()) { if("toUpload0".equals(entry.getKey())) { file1=true; String pathName = fileUploadPath+entry.getValue().getOriginalFilename(); pathNames[0] = pathName; ins[0] = entry.getValue().getInputStream(); storyDTO.setPosterUrl1(pathName); } if("toUpload1".equals(entry.getKey())) { file2=true; String pathName = fileUploadPath+entry.getValue().getOriginalFilename(); pathNames[1] = pathName; ins[1] = entry.getValue().getInputStream(); storyDTO.setPosterUrl2(pathName); } if("toUpload2".equals(entry.getKey())) { file3=true; String pathName = fileUploadPath+entry.getValue().getOriginalFilename(); pathNames[2] = pathName; ins[2] = entry.getValue().getInputStream(); storyDTO.setPosterUrl3(pathName); } if("toUpload3".equals(entry.getKey())) { file4=true; String pathName = fileUploadPath+entry.getValue().getOriginalFilename(); pathNames[3] = pathName; ins[3] = entry.getValue().getInputStream(); storyDTO.setPosterUrl4(pathName); } } if(null == storyDTO.getStoryId()) { this.saveStory(storyDTO); } else { String[] deletePaths = new String[4]; StoryDTO oldStoryDTO = this.getStoryById(storyDTO.getStoryId()); if(file1==null && StringUtils.isBlank(fileName[0])){ deletePaths[0]=oldStoryDTO.getPosterUrl1(); storyDTO.setPosterEmptyUrl1("Y"); } if(file2==null && StringUtils.isBlank(fileName[1])){ deletePaths[1]=oldStoryDTO.getPosterUrl2(); storyDTO.setPosterEmptyUrl2("Y"); } if(file3==null && StringUtils.isBlank(fileName[2])){ deletePaths[2]=oldStoryDTO.getPosterUrl3(); storyDTO.setPosterEmptyUrl3("Y"); } if(file4==null && StringUtils.isBlank(fileName[3])){ deletePaths[3]=oldStoryDTO.getPosterUrl4(); storyDTO.setPosterEmptyUrl4("Y"); } StoryUpdateDTO updateDTO = new StoryUpdateDTO(); BeanUtils.copyProperties(updateDTO, storyDTO); updateDTO.setUpdateStoryId(storyDTO.getStoryId()); this.updateStory(updateDTO); for (String path : deletePaths) { if(StringUtils.isNotBlank(path)) { FileOprUtils.deleteFile(path); } } } for(int i = 0; i < pathNames.length; i++) { FileOprUtils.copyFileToServerPath(pathNames[i], ins[i]); } }
最后是Spring的声明式事务配置:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref bean="dataSource" /> </property> <property name="rollbackOnCommitFailure" value="true" /> <property name="globalRollbackOnParticipationFailure" value="true" /> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" rollback-for="Exception" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.focoon.ds..service.*.*(..))" /> </aop:config>
注意需要使用rollback-for属性明确指出抛出哪种异常需要回滚