1. 引言
每位web开发者在工程中都必须实现至少实现一个客户文件的上载功能。永远需要!然而,要求用户仅提供一个指向其数据的url是不公平的。作为一个开发者,帮助用户顺利地完成这些正是你的工作。http协议的使用将十分有助于解决这个问题,但是许多开发者并没有选择使用http协议。
你需要解决的问题有:存储上载的文件并找到关于问题"where?","why?"和"how?"等等的有关答案。
本文将解释所有在解决这些问题中遇到的瓶颈,并提供了功能性的、易于理解的代码,这些内容很可能会应用于你将来的工程中。
2. 准备工作
本文将使用当前最流行的开发工具,它们是:
?应用程序服务器:weblogic 8.1 sp3服务器
?基于java的构建工具:apache ant 1.6.2
?数据库服务器:mysql 4.0.16
?用于从java连接到mysql的:mysql connector/j 3.1.7
struts 1.2.4用作构建java web应用程序的框架,而hibernate 3.0(rc1)用于对象/关系持续性操作和查询服务。
本文虽然基于windows平台写成,但在其它操作系统之上,应该稍作修改就能运行。
另外,读者还应熟悉bea weblogic服务器以及使用struts和hibernate进行j2ee应用程序的开发。篇幅所限,本文并没有讨论关于应用程序和数据库服务器配置的问题。
3. 上载工程分析
现在让我们讨论存储上载文件的机制,并回答上面列出的三个问题。
?where?你将会把上载文件存储到一个数据库中。
?why?在许多情况下,它确实是合适的解决方案。使用本文的解决方案,你不会因同步上载文件而烦恼,一旦你正在备份着应用程序-你只需要备份数据库就可以了。而且,你不需要与一个用户及其在一文件系统上的文件一直保持十分笨拙的联系。
?how?可以使用blob(二进制大型对象)字段实现。这样的字段用于存储大型的并且经常是原始或二进制的格式。hibernate可以使你非常容易地操作这些字段。
典型情况下,一个企业应用程序(ear)由两部分组成:web层(war)和商业层(ejb)。商业层包含一个无状态的会话bean-它借助于hibernate的帮助实现数据的存储。图1显示了ejb的远程接口。
![]() 图1.hellosession ejb的接口。 |
从web层角度看,这个ejb为商业代理所存取。
注意,该代码使用了一个类user的对象。user代表什么意思?它是一个保留在数据库中的用户实体的"plain old java object"(pojo)。你将会活跃地使用这个userpojo。设置它的属性并请求ejb来存储它,然后带回一个所有的已存在于数据库中的user实体的列表。或者,由它取回一个专门的user实体并存入pojo中,然后使用一个getter存取器来存取它的属性(见图2)。
![]() 图2.所有的web层的servlet都使用该user pojo。 |
非常明显,web层仅由三个servlet(struts action的)组成,一个用于上载文件,一个用于下载文件,一个用于列出所有的user实体及其相关文件。
?downloadfileaction:该servlet仅使用一个参数id,这是在数据库中的一个用户的id。然后,它装入该用户的实体并把该文件从blob字段传递到servletoutputstream。
?uploadfileaction:该servlet负责从一个html表单中提取数据并用这些数据进一步生成dynaactionform。它仅提取用户名和上载的文件。
?listallfilesaction:该servlet没有输入参数或数据,仅负责从数据库中装入所有可用的用户user实体。 4. 环境准备
如果所有相应的软件被正确下载并安装在你的pc上,那么下一步,你就可以准备数据库和存取该数据库的用户而且还要使用名为mysqlds的jndi设置好连接池与数据源。同时,请肯定mysql connector/j存在于你的类路径中!要检查这一点,只需输入:
echo %classpath%
文件mysql-connector-java-3.1.7-bin.jar(带有完整的路径)应该于此。这是必需的,因为weblogic需要查找到mysql connector/j并用该驱动程序进行工作。
现在,既然一切准备妥当,我们就可以在数据库中创建一个表以用于保存用户实体。以任何mysql客户身份登录到该数据库,然后输入:
| drop table if exists table_user_file; create table table_user_file ( user_id int auto_increment not null, user_username text, user_filename text, user_filetype text, user_filesize text, user_filebin longblob, primary key (user_id) ) type = myisam; |
你看,一切都很简单。也许在此你最陌生的是longblob类型。二进制大型对象(blob)列是mysql的秘密武器之一。这些列中存储了二进制的数据,你可以象其它普通的数据类型一样来检索和操纵它。根据mysql指南有关资料,blob是一个二进制大型对象,它能容纳不同大小的数据。事实上,mysql有四种blob类型:
?tinyblob:仅255个字符
?blob:最大限制到65k字节
?mediumblob:限制到16m字节
?longblob:可达4gb
在每个mysql的文档(从mysql4.0开始)的介绍中,一个longblob列的最大允许长度依赖于在客户/服务器协议中可配置的最大包的大小和可用内存数。
你可能对在blob中存储大型文件非常谨慎,但是请放心使用,mysql提供了这样的灵活性!最大包的大小可容易地通过文件my.ini中的适当行进行设置。例如:
set-variable = max_allowed_packet=10m
你能指定几乎任何你需要的大小。默认是1m。
现在,在hibernate和weblogic之间还存在一个问题。根据hibernate的文档,hibernate3.0使用antlr作为它的新的查询分析器。这真是个遗憾!但是bea weblogic在系统类路径中包括了antlr的一个版本,它在任何应用程序库装入前就已经被加载了。因为weblog似乎不支持属性类装载器隔离,在应用程序的上下文中它是不会看到该hibernate类的。bea好象在包名前加上前缀来解决这个问题,但是现已发布的antlr并没有这个前缀。
这个问题的解决办法是,把所有的hibernate和依赖库放到你的classpath中。就象如下这样:
c:/green/te3>echo %classpath%
.;c:/mysql-connector-java-3.1.7/mysql-connector-java-3.1.7-bin.jar;c:/hibernate-3.0/hibernate3.jar;
c:/hibernate-3.0/lib/ehcache-1.1.jar;c:/hibernate-3.0/lib/jta.jar;c:/hibernate-3.0/lib/xml-apis.jar;
c:/hibernate-3.0/lib/commons-logging-1.0.4.jar;c:/hibernate-3.0/lib/dom4j-1.5.2.jar;
c:/hibernate-3.0/lib/antlr-2.7.4.jar;c:/hibernate-3.0/lib/cglib-full-2.0.2.jar;
c:/hibernate-3.0/lib/jdbc2_0-stdext.jar;c:/hibernate-3.0/lib/xerces-2.6.2.jar;
c:/hibernate-3.0/lib/jaxen-1.1-beta-4.jar;c:/hibernate-3.0/lib/commons-collections-2.1.1.jar;
c:/hibernate-3.0/lib/log4j-1.2.9.jar;
现在,unzip源代码到任何你想要的目录下。用你喜欢的文件编辑器打开build.xml文件,并检查(如果必要的话,加以改变)前面涉及你的home目录的几行和你的域的标题。保存你的变化并输入:
ant
当工程建构完成时,你就会得到一个文件te3.ear,这是一个准备好等待发布的包(名字te3仅是个普通名字)。然后,你就可以用weblogic的管理控制台发布它,当发布后,用你的浏览器http://localhost:7001/te3/打开它。之后,你将看到两个选项:"upload file"和"list all files"。 5. 代码分析
现在,你已经看到了一切是如何工作的,下面解释一下几个更为重要的代码片断。如前述,uploadfileaction.java使用dynaactionform来保持html表单的属性。下面是它在/web-inf/struts-config.xml文件中的定义:
| ... <form-beans> <form-bean name="uploadfileform" type="org.apache.struts.action.dynaactionform" dynamic="true"> <form-property name="myfile" type="org.apache.struts.upload.formfile"/> <form-property name="myname" type="java.lang.string"/> </form-bean> </form-beans> ... <action-mappings> <action path="/uploadfile" type="com.prokhorenko.web.uploadfileaction" name="uploadfileform"> ... |
下面的代码教你怎样通过属性存取来"存储"一个上传的文件:
| ... user per = new user(); dynaactionform df = (dynaactionform) form; formfile myfile = (formfile) df.get("myfile"); ... per.setfilebin ( hibernate.createblob (myfile.getinputstream()) ); ... |
hibernate.createblob(...)返回初始的不变的java.sql.blob对象并使用它,因为为了设置user实体的filebin属性,该属性被定义并被映射为java.sql.blob。
接下去一段有趣的代码来自于downloadfileaction.java,它用’id’加载该user实体:
| ... user per = bd.getuser( new long((string)request.getparameter("id")) ); ... |
下一步,你就需要设置响应的头部,并开始把blob型字段filebin的内容写到servletoutputstream中:
| ... servletoutputstream outstream = response.getoutputstream(); inputstream in = per.getfilebin().getbinarystream(); byte[] buffer = new byte[32768]; int n = 0; while ( ( n = in.read(buffer)) != -1) { outstream.write(buffer, 0, n); } in.close(); outstream.flush(); ... |
6. 最容易的解决方案
在所有官方档案中,hibernate 3.0都包装了blob和clob的实例,这样以来就允许具有类型blob或clob的属性的类可以被分离、串行化、反串行化以及被传递而实现合并的目的。因此,你会看到,struts和hibernate几乎为你做了一切事情-而需要你做的仅仅是极少的几个步骤。
现在看来,上载文件并把它们存储到数据库中已不再是象以前那样是一项繁重的任务。你只需选择正确的工具并知道如何灵活地使用它们即可!


闽公网安备 35060202000074号