提交表单中有文件上传后台如何保证数据的一致性

在公司开发一个后台管理系统时有这样的需求:提交一个表单时,要把表单域内容和上传的文件内容(可以是多个上传文件)一并提交到后台去,并且数据库持久化失败后数据要回滚且文件不应该上传上去,如果文件上传失败同样数据库也要回滚。

我的做法是:

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属性明确指出抛出哪种异常需要回滚

相关推荐