CMS项目总结:18、文件上传commons-fileupload
commons-fileupload包依赖于commons-io
<form action="ArticleServlet" method="post" enctype="multipart/form-data"> 涉及到上传文件一定要在form中定义enctype="multipart/form-data">
<input type="file" name="attachs" id="attachs"> 类型叫file。 在定义enctype=“multipart/form-data”后即使是普通的表单域也不能通过request.getParameter()来获取。
由于原先的代码中已经使用了很多的request.getParameter()方法,所以不适合改动所有的这个方法,我们需要想个新技巧来在不改动或者很少改动原先代码的情况下实现文件上传的功能。
实际上HttpServletRequest是个interface,所以在doPost、doGet那些方法中用的肯定不是HttpServletRequest的实例(因为接口是没有实例的),那里的request是接口的某个具体实现,这个实现是由容器tomcat自动创建的,实际上调用的是RequestFacade(实现了HttpServletRequest接口的类,多态)
现在如果上传的form中有file,enctype="multipart/form-data">,先在BaseServlet(访问各种ArticleServlet、ChannelServlet的入口)中用MultipartRequestWrap中的request代替原先的request,待会儿在仔细的研究关于具体MultipartRequestWrap,代替的代码如下:
boolean isMultipart = ServletFileUpload.isMultipartContent(request);判断request是否是multipart类型,如果是的话request = new MultipartRequestWrapper(request);用MultipartRequestWrapper中的request来替换原先的request(RequestFacade)。
关于用MultipartRequestWrapper代替RequestFacade,是用到了Decorator设计模式。
HttpServletRequestWrapper没有默认的无参的构造方法
还需要创建Attachment(附件)的Bean类,同时添加到Article的Bean中:
以及在数据库中创建attachment的table
接下来进入正题:
1、分析MultipartRequestWrap的代码:在MultipartRequestWrapper中实现了request的多态替换,原有的代码不需要改动就能继续使用request.getParameter()、request.getParameterMap()。(不替换前如果form中有文件上传,那么request.getParameter()、request.getParameterMap()方法是不能用的)。这段代码就不逐行分析了,将来用的时候在看吧,不难。
表单域中可能存在同名name的(譬如多选的下拉选择框,当选中多个时(相同的name)),这时就用到
这段代码。
把Attachment数据放入request的代码:
2、将Attachment插入Article中;ArticleServlet中添加的代码:
我们原先是这么实现的:
为了往Article中添加一些Attachment,在ArticleServlet中创建List,再调用setAttachment()。而更好的实现应该是:
避免了在ArticleServlet中做复杂的操作,同时在ArticleServlet中不需要知道attachments是个List还是Set……。好处还有可以由Article决定如何添加Attachment。注意这里有个GRASP模式(GRASP模式中的专家模式,专家模式:一个职责应该放在具有这个职责所需信息的类中,往Article中添加Attachment的职责应该放在具有Attachment的Article类中。)
3、添加文章时往数据库中填充t_attachment表中的字段,删除文章时将文章对应的附件信息从t_attachment表中删除1、在ArticleDaoForMyBatisImpl中添加文章(及附件)的代码:
在Article.xml中插入t_attachment的代码:
2、在ArticleDaoForMyBatisImpl中删除文章(及附件)的代码:
由于删除文章的同时删除附件,所以我们需要根据aritcle的id来找出article,再找出attachments和channels,原本可以再单独调用findAttachmentByArticle,但是我们用了这种resultMap的简便办法,通过一次调用Article a = (Article) session.selectOne(Article.class.getName()+".findById", articleId);就根据article的id字段取出t_channels和t_attachments表中的相关数据,放到Article的channels、attachments属性中。
附件的在硬盘中的存储信息是放在t_attachment中的,所以我们根据List attachments = a.getAttachments();取出文章的信息,然后调用new File(realPath).delete();删除硬盘中的附件信息。最后再
//删除数据库中的相关记录
session.delete(Article.class.getName() + ".del_attachments_by_articleId", articleId);
//删除文章
session.delete(Article.class.getName()+".del", articleId);
注意顺序一定要对,先删了硬盘的数据,再删数据库中的t_attachment,最后再删t_article中的数据,先删对象的关联,再删对象自己。(因为如果先删了t_attachment,就找不到附件存储在硬盘的地址信息了,先删t_article,那么就没有articleid了,也不好删t_attachment了)。
3、在ArticleDaoForMyBatisImpl中不删文章,只是单独删除附件:
ArticleServlet中删除附件的代码:
ArticleDaoForMyBatisImpl中的代码:
Article.xml文件中的代码: