9.6 上传文件到服务器
在当今移动互联网时代使用上传头像、资料等功能是相当频繁的,本场景主要介绍 Spring MVC 下上传文件的实例。一般而言,我们上传的文件都不会保存到数据库中而是保存在文件服务器上,而把文件的路径保存到数据库中,需要我们注意一些细节,以提高系统性能。
这里我们先看一个糟糕的文件上传实例,读者请思考一下为什么它是糟糕的。我们创建页面,这里使用的是 easyui,其实使用普通的 HTML 也可以看到效果,如代码清单 9-41 所示。
代码清单 9-41:文件上传 HTML
<%@page contentType="text/html" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http:// www.w3.org/TR/html4/loose.dtd"> <% String serverName = request.getServerName(); int port = request.getServerPort(); String context = request.getContextPath(); String basePath = "http://" + serverName + ":" + port + context; %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <link rel="stylesheet" href="<%=basePath%>/easyui/themes/icon.css" type="text/css"></link> <link rel="stylesheet" href="<%=basePath%>/easyui/themes/default/ easyui.css" type="text/css"></link> <script type="text/javascript" src="<%=basePath%>/easyui/jquery. min.js"></script> <script type="text/javascript" src="<%=basePath%>/easyui/jquery. easyui.min.js"></script> <script type="text/javascript" src="<%=basePath%>/easyui/locale/ easyui-lang-zh_CN.js"></script> </head> <body> <div class="easyui-panel" title="New Topic" style="width:400px"> <div style="padding:10px 60px 20px 60px"> <form id="fileForm" method="post" enctype="multipart/form- data"> <table cellpadding="5"> <tr> <td>image name:</td> <td><input class="easyui-textbox" name="title" id="title" data-options="required:true"></input></td> </tr> <tr> <td>select file:</td> <td><input class="easyui-filebox" id="imageFile" name="imageFile" data-options="required:true, prompt:'Choose a file...'"> </input></td> </tr> </table> </form> <div style="text-align:center;padding:5px"> <a href="javascript:void(0)" class="easyui-linkbutton" onclick="submitForm()">commit</a> <a href="javascript:void(0)" class="easyui-linkbutton" onclick="clearForm()">reset</a> </div> </div> </div> <script> function submitForm() { $('#fileForm').form('submit',{ url: "../file/upload.do", onSubmit: function(){ return true; }, success: function(result, a, b){ var jsonResult = $.parseJSON(result); alert(jsonResult.info); } }); } </script> </body> </html>
现在我们搭建 Spring MVC 控制器,它主要提供访问。当我们的请求提交的时候,Spring MVC 控制器会根据 RequestMapping 的配置跳转到这个控制器上,如代码清单 9-42 所示。
代码清单 9-42:上传文件控制器
@Controller public class FileController { @Autowired private FileService fileService = null; @RequestMapping(value = "/file/upload", method = RequestMethod.POST) @ResponseBody public Message uploadFile(@RequestParam("title") String title, HttpServletRequest request, ModelMap model) throws IOException { MultipartHttpServletRequest multipartRequest = (MultipartHttp ServletRequest) request; MultipartFile imgFile = multipartRequest.getFile("imageFile"); FileBean file = new FileBean(); file.setTitle(title); Message msg = new Message(); if (fileService.insertFile(imgFile, file)) { msg.setSuccess(true); msg.setInfo("插入成功"); } else { msg.setSuccess(false); msg.setInfo("插入失败"); } return msg; } private class Message { private boolean success = false; private String info = null; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } } }
完成上面的操作后就要上传具体的服装类了。这里会用到的服务类有 Service 接口和 Service 实现类,它们主要提供上传和记录数据库信息的操作,如代码清单 9-43 所示。
代码清单 9-43:上传文件具体服务类
@Service public class FileServiceImpl implements FileService { @Autowired private FileDAO fileDAO = null; @Override @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) public boolean insertFile(MultipartFile imgFile, FileBean file) { String filePath = "F:/mybatis-files/" + new Date().getTime() + imgFile.getOriginalFilename(); file.setFilePath(filePath); fileDAO.insertFile(file); this.uploanFile(imgFile, filePath); return true; } private void uploanFile(MultipartFile imgFile, String filePath) { FileOutputStream os = null; FileInputStream in = null; try { os = new FileOutputStream(filePath); in = (FileInputStream) imgFile.getInputStream(); byte []b = new byte[1024]; while(in.read(b) != -1){ os.write(b); } os.flush(); os.close(); in.close(); } catch (Exception ex) { Logger.getLogger(FileServiceImpl.class.getName()).log (Level.SEVERE, null, ex); //上传失败则抛出异常回滚事务 throw new RuntimeException("文件上传失败。"); } finally { try { if (os != null) { os.close(); } } catch (IOException ex) { Logger.getLogger(FileController.class.getName()).log(Level. SEVERE, null, ex); } try { if (in != null) { in.close(); } } catch (IOException ex) { Logger.getLogger(FileController.class.getName()).log(Level. SEVERE, null, ex); } } } }
我们看看这个 insertFile 方法,我们先插入数据到数据库,然后进行写入文件到服务器的操作,貌似一切都很正常,逻辑也没有问题,但是笔者必须告诉你这是一段糟糕的代码。为什么呢?请思考一下。
在互联网时代,尤其是高性能的网站系统一个最重要的资源是数据库连接资源。如果不能准确关闭它们,那么系统将是一个低性能的系统。如果你对 Spring 了解,就应该明白 Spring 的数据库事务存活在 Service 层,从第一条 SQL 的执行就打开了数据库连接资源,直至方法结束。这里我们插入了一条数据到数据库,但是在我们将文件写入服务器的过程中,Spring 并没有关闭数据库资源,直至上传成功,方法结束,Spring 才会关闭数据库资源,试想如果有多个用户在上传大型文件,或者在高并发的环境中,多个上传文件的服务同时进行,就会占用大量的数据库连接,这时就极其容易宕机。这就是一个典型的没有正确关闭数据库连接资源引发系统的瓶颈问题,下面我们用示意图来描述它,这样会更加直观一些,如图 9-1 所示。
图 9-1 错误上传文件示意图
为了避免这些文件上传的代码应该考虑剥离文件上传的代码到 Controller 中去,让我们看看重写 Controller 上传文件的方法,如代码清单 9-44 所示。
代码清单 9-44:重写 Controller 上传文件的方法
@RequestMapping(value = "/file/upload", method = RequestMethod.POST) @ResponseBody public Message uploadFile(@RequestParam("title") String title, HttpServletRequest request, ModelMap model) throws IOException { MultipartHttpServletRequest multipartRequest = (MultipartHttp ServletRequest) request; MultipartFile imgFile = multipartRequest.getFile("imageFile"); FileBean file = new FileBean(); String filePath = "F:/mybatis-files/" + new Date().getTime() + imgFile.getOriginalFilename(); this.uploanFile(imgFile, filePath); file.setFilePath(filePath); file.setTitle(title); Message msg = new Message(); if (fileService.insertFile(imgFile, file)) { msg.setSuccess(true); msg.setInfo("插入成功"); } else { msg.setSuccess(false); msg.setInfo("插入失败"); } return msg; }
这里我们先上传了文件,然后调度 insertFile 方法,显然数据库的事务打开时间和文件上传服务器没有交集,避免了数据库事务长期得不到释放的可能性,这样就大大降低了宕机的可能性,提高了系统性能,如图 9-2 所示。
除了文件操作,还有其他的一些与数据库无关的操作也是值得我们注意的,在一些没有必要使用到数据库资源的操作上,应该尽量避免数据库连接资源被占用。
图 9-2 正确上传文件的示意图
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论