文档库 最新最全的文档下载
当前位置:文档库 › SSH实现上传下载

SSH实现上传下载

SSH实现上传下载
SSH实现上传下载

Struts Spring Hibernate实现上传下载

https://www.wendangku.net/doc/6618294289.html, 2005年12月21日 10:06 天极yesky

引言

文件的上传和下载在J2EE编程已经是一个非常古老的话题了,也许您马上就能掰着指头数出好几个著名的大件:如SmartUpload、Apache的FileUpload。但如果您的项目是构建在Struts+Spring+Hibernate(以下称SSH)框架上的,这些大件就显得笨重而沧桑了,SSH提供了一个简捷方便的文件上传下载的方案,我们只需要通过一些配置并辅以少量的代码就可以完好解决这个问题了。

本文将围绕SSH文件上传下载的主题,向您详细讲述如何开发基于SSH的Web程序。SSH 各框架的均为当前最新版本:

·Struts 1.2

·Spring 1.2.5

·Hibernate 3.0

本文选用的数据库为Oracle 9i,当然你可以在不改动代码的情况下,通过配置文件的调整将其移植到任何具有Blob字段类型的数据库上,如MySQL,SQLServer等。

总体实现

上传文件保存到T_FILE表中,T_FILE表结构如下:

图 1 T_FILE表结构

其中:

·FILE_ID:文件ID,32个字符,用Hibernate的uuid.hex算法生成。

·FILE_NAME:文件名。

·FILE_CONTENT:文件内容,对应Oracle的Blob类型。

·REM ARK:文件备注。

文件数据存储在Blob类型的FILE_CONTENT表字段上,在Spring中采用OracleLobHandler来处理Lob字段(包括Clob和Blob),由于在程序中不需要引用到oracle 数据驱动程序的具体类且屏蔽了不同数据库处理Lob字段方法上的差别,从而撤除程序在多数据库移植上的樊篱。

1.首先数据表中的Blob字段在Java领域对象中声明为byte[]类型,而非java.sql.Blob 类型。

2.数据表Blob字段在Hibernate持久化映射文件中的type为

org.springframework.orm.hibernate3.support.BlobByteArrayType,即Spring所提供的用户自定义的类型,而非java.sql.Blob。

3.在Spring中使用org.springframework.jdbc.support.lob.OracleLobHandler处理Oracle数据库的Blob类型字段。

通过这样的设置和配置,我们就可以象持久化表的一般字段类型一样处理Blob字段了。

以上是Spring+Hibernate将文件二进制数据持久化到数据库的解决方案,而Struts

通过将表单中file类型的组件映射为ActionForm中类型为org.apache.struts.upload. FormFile的属性来获取表单提交的文件数据。

综上所述,我们可以通过图 2,描绘出SSH处理文件上传的方案:

图 2 SSH处理文件上传技术方案文件上传的页面如图 3所示:

图 3 文件上传页面

文件下载的页面如图 4所示:

图 4 文件下载页面该工程的资源结构如图 5所示:

图 5 工程资源结构

工程的类按SSH的层次结构划分为数据持久层、业务层和Web层;WEB-INF下的applicationContext.xml为Spring的配置文件,struts-config.xml为Struts的配置文件,file-upload.jsp为文件上传页面,file-list.jsp为文件列表页面。

本文后面的章节将从数据持久层->业务层->Web层的开发顺序,逐层讲解文件上传下载的开发过程。

数据持久层

1、领域对象及映射文件

您可以使用Hibernate Middlegen、HIbernate Tools、Hibernate Syhchronizer等工

具或手工的方式,编写Hibernate的领域对象和映射文件。其中对应T_FILE表的领域对象Tfile.java为:

代码 1 领域对象Tfile

特别需要注意的是:数据库表为Blob类型的字段在Tfile中的fileContent类型为byte[]。Tfile的Hibernate映射文件Tfile.hbm.xml放在Tfile .java类文件的相同目录下:

代码 2 领域对象映射文件

fileContent字段映射为Spring所提供的BlobByteArrayType类型,

BlobByteArrayType是用户自定义的数据类型,它实现了Hibernate 的

https://www.wendangku.net/doc/6618294289.html,erType接口。BlobByteArrayType使用从sessionFactory获取的Lob操作句柄lobHandler将byte[]的数据保存到Blob数据库字段中。这样,我们就再没有必要通过硬编码的方式,先insert然后再update来完成Blob类型数据的持久化,这个原来难伺候的老爷终于被平民化了。关于lobHandler的配置请见本文后面的内容。

此外lazy="true"说明地返回整个Tfile对象时,并不返回fileContent这个字段的数据,只有在显式调用tfile.getFileContent()方法时才真正从数据库中获取fileContent 的数据。这是Hibernate3引入的新特性,对于包含重量级大数据的表字段,这种抽取方式提高了对大字段操作的灵活性,否则加载Tfile对象的结果集时如果总是返回fileContent,这种批量的数据抽取将可以引起数据库的"洪泛效应"。

2、DAO编写和配置

Spring强调面向接口编程,所以我们将所有对Tfile的数据操作的方法定义在TfileDAO 接口中,这些接口方法分别是:

·findByFildId(String fileId)

·save(Tfile tfile)

·List findAll()

TfileDAOHibernate提供了对TfileDAO接口基于Hibernate的实现,如代码 3所示:

代码 3 基于Hibernate 的fileDAO实现类

TfileDAOHibernate通过扩展Spring提供的Hibernate支持类HibernateDaoSupport 而建立,HibernateDaoSupport封装了HibernateTemplate,而HibernateTemplate封装了Hibernate所提供几乎所有的的数据操作方法,如execute(HibernateCallback action),load(Class entityClass, Serializable id),save(final Object entity)等等。

所以我们的DAO只需要简单地调用父类的HibernateTemplate就可以完成几乎所有的数据库操作了。

由于Spring通过代理Hibernate完成数据层的操作,所以原Hibernate的配置文件hibernate.cfg.xml的信息也转移到Spring的配置文件中:

代码 4 Spring中有关Hibernate的配置信息

第3~9行定义了一个数据源,其实现类是apache的BasicDataSource,第11~25行定义了Hibernate的会话工厂,会话工厂类用Spring提供的LocalSessionFactoryBean维护,它注入了数据源和资源映射文件,此外还通过一些键值对设置了Hibernate所需的属性。

其中第16行通过类路径的映射方式,将sshfile.model类包目录下的所有领域对象的映射文件装载进来,在本文的例子里,它将装载进Tfile.hbm.xml映射文件。如果有多个映射文件需要声明,使用类路径映射方式显然比直接单独指定映射文件名的方式要简便。

第27~30行定义了Spring代理Hibernate数据操作的HibernateTemplate模板,而第32~34行将该模板注入到tfileDAO中。

需要指定的是Spring 1.2.5提供了两套Hibernate的支持包,其中Hibernate 2相关的封装类位于org.springframework.orm.hibernate2.*包中,而Hibernate 3.0的封装类

位于org.springframework.orm.hibernate3.*包中,需要根据您所选用Hibernate版本进行正确选择。

3、Lob字段处理的配置

我们前面已经指出Oracle的Lob字段和一般类型的字段在操作上有一个明显的区别--那就是你必须首先通过Oracle的empty_blob()/empty_clob()初始化Lob字段,然后获取该字段的引用,通过这个引用更改其值。所以要完成对Lob字段的操作,Hibernate必须执行两步数据库访问操作,先Insert再Update。

使用BlobByteArrayType字段类型后,为什么我们就可以象一般的字段类型一样操作Blob字段呢?可以确定的一点是:BlobByteArrayType不可能逾越Blob天生的操作方式,原来是BlobByteArrayType数据类型本身具体数据访问的功能,它通过LobHandler将两次数据访问的动作隐藏起来,使Blob字段的操作在表现上和其他一般字段业类型无异,所以LobHandler即是那个"苦了我一个,幸福十亿人"的那位幕后英雄。

LobHandler必须注入到Hibernate会话工厂sessionFactory中,因为sessionFactory 负责产生与数据库交互的Session。LobHandler的配置如代码 5所示:

代码 5 Lob字段的处理句柄配置

首先,必须定义一个能够从连接池中抽取出本地数据库JDBC对象(如

OracleConnection,OracleResultSet等)的抽取器:nativeJdbcExtractor,这样才可以执行一些特定数据库的操作。对于那些仅封装了Connection而未包括Statement的简单数据连接池,SimpleNativeJdbcExtractor是效率最高的抽取器实现类,但具体到apache的BasicDataSource连接池,它封装了所有JDBC的对象,这时就需要使用CommonsDbcpNativeJdbcExtractor了。Spring针对几个著名的Web服务器的数据源提供了相应的JDBC抽取器:

·WebLogic:WebLogicNativeJdbcExtractor

·WebSphere:WebSphereNativeJdbcExtractor

·JBoss:JBossNativeJdbcExtractor

在定义了JDBC抽取器后,再定义lobHandler。Spring 1.2.5提供了两个lobHandler:

·DefaultLobHandler:适用于大部分的数据库,如SqlServer,MySQL,对Oracle 10g 也适用,但不适用于Oracle 9i(看来Oracle 9i确实是个怪胎,谁叫Oracle 公司自己都说Oracle 9i是一个过渡性的产品呢)。

·OracleLobHandler:适用于Oracle 9i和Oracle 10g。

由于我们的数据库是Oracle9i,所以使用OracleLobHandler。

在配置完LobHandler后,还需要将其注入到sessionFactory的Bean中,下面是调用后的sessionFactory Bean的配置:

代码 6 将lobHandler注入到sessionFactory中的配置

如第7所示,通过sessionFactory的lobHandler属性进行注入。

业务层

1、业务层接口

"面向接口而非面向类编程"是Spring不遗余力所推荐的编程原则,这条原则也已经为大部开发者所接受;此外,JDK的动态代理只对接口有效,否则必须使用CGLIB生成目标类的子类。我们依从于Spring的倡导为业务类定义一个接口:

代码 7 业务层操作接口

其中save(FileActionForm fileForm)方法,将封装在fileForm中的上传文件保存到数据库中,这里我们使用FileActionForm作为方法入参,FileActionForm是Web层的表单数据对象,它封装了提交表单的数据。将FileActionForm直接作为业务层的接口入参,相当于将Web层传播到业务层中去,即将业务层绑定在特定的Web层实现技术中,按照分层模型学院派的观点,这是一种反模块化的设计,但在"一般"的业务系统并无需提供多种UI界面,系统Web层将来切换到另一种实现技术的可能性也微乎其微,所以笔者觉得没有必要为了这个业务层完全独立于调用层的过高目标而去搞一个额外的隔离层,浪费了原材料不说,还将系统搞得过于复杂,相比于其它原则,"简单"始终是最大的一条原则。

getAllFile()负责获取T_FILE表所有记录,以便在网页上显示出来。

而getFileName(String fileId)和write(OutputStream os,String fileId)则用于下载某个特定的文件。具体的调用是将Web层将response.getOutputStream()传给

write(OutputStream os,String fileId)接口,业务层直接将文件数据输出到这个响应流中。具体实现请参见错误!未找到引用源。节下载文件部分。

2、业务层接口实现类

FileService的实现类为FileServiceImpl,其中save(FileActionForm fileForm)的实现如下所示:

代码 8 业务接口实现类之save()

在save(FileActionForm fileForm)方法里,完成两个步骤:

其一,象在水桶间倒水一样,将FileActionForm对象中的数据倒入到Tfile对象中;

其二,调用TfileDAO保存数据。

需要特别注意的是代码的第11行,FileActionForm的fileContent属性为

org.apache.struts.upload.FormFile类型,FormFile提供了一个方便的方法

getFileData(),即可获取文件的二进制数据。通过解读FormFile接口实现类DiskFile的原码,我们可能知道FormFile本身并不缓存文件的数据,只有实际调用getFileData()时,才从磁盘文件输入流中获取数据。由于FormFile使用流读取方式获取数据,本身没有缓存文件的所有数据,所以对于上传超大体积的文件,也是没有问题的;但是,由于数据持久层的Tfile使用byte[]来缓存文件的数据,所以并不适合处理超大体积的文件(如100M),对于超大体积的文件,依然需要使用java.sql.Blob类型以常规流操作的方式来处理。

此外,通过FileForm的getFileName()方法就可以获得上传文件的文件名,如第21行代码所示。

write(OutputStream os,String fileId)方法的实现,如代码 9所示:

代码 9 业务接口实现类之write()

write(OutputStream os,String fileId)也简单地分为两个操作步骤,首先,根据fileId加载表记录,然后将fileContent写入到输出流中。

3、Spring事务配置

下面,我们来看如何在Spring配置文件中为FileService配置声明性的事务

Spring的事务配置包括两个部分:

其一,定义事务管理器transactionManager,使用HibernateTransactionManager实现事务管理;

其二,对各个业务接口进行定义,其实txProxyTemplate和fileService是父子节点的关系,本来可以将txProxyTemplate定义的内容合并到fileService中一起定义,由于我们的系统仅有一个业务接口需要定义,所以将其定义的一部分抽象到父节点txProxyTemplate 中意义确实不大,但是对于真实的系统,往往拥有为数众多的业务接口需要定义,将这些业务接口定义内容的共同部分抽取到一个父节点中,然后在子节点中通过parent进行关联,就可以大大简化业务接口的配置了。

父节点txProxyTemplate注入了事务管理器,此外还定义了业务接口事务管理的方法(允许通过通配符的方式进行匹配声明,如前两个接口方法),有些接口方法仅对数据进行读操作,而另一些接口方法需要涉及到数据的更改。对于前者,可以通过readOnly标识出来,这样有利于操作性能的提高,需要注意的是由于父类节点定义的Bean仅是子节点配置信息的抽象,并不能具体实现化一个Bean对象,所以需要特别标注为abstract="true",如第8行所示。

fileService作为一个目标类被注入到事务代理器中,而fileService实现类所需要的tfileDAO实例,通过引用3.2节中定义的tfileDAO Bean注入。

Web层实现

1、Web层的构件和交互流程

Web层包括主要3个功能:

·上传文件。

·列出所有已经上传的文件列表,以供点击下载。

·下载文件。

Web层实现构件包括与2个JSP页面,1个ActionForm及一个Action:

·file-upload.jsp:上传文件的页面。

·file-list.jsp:已经上传文件的列表页面。

·FileActionForm:file-upload.jsp页面表单对应的ActionForm。

·FileAction:继承org.apache.struts.actions.DispatchAction的Action,这样这个Action就可以通过一个URL参数区分中响应不同的请求。

Web层的这些构件的交互流程如图 6所示:

图 6 Web层Struts流程图

其中,在执行文件上传的请求时,FileAction在执行文件上传后,forward到loadAllFile出口中,loadAllFile加载数据库中所有已经上传的记录,然后forward到名为fileListPage的出口中,调用file-list.jsp页面显示已经上传的记录。

2、FileAction功能

Struts 1.0的Action有一个弱项:一个Action只能处理一种请求,Struts 1.1中引入了一个DispatchAction,允许通过URL参数指定调用Action中的某个方法,如

http://yourwebsite/fileAction.do?method=upload即调用FileAction中的upload方法。通过这种方式,我们就可以将一些相关的请求集中到一个Action当中编写,而没有必要为某个请求操作编写一个Action类。但是参数名是要在struts-config.xml中配置的:

第6行的parameter="method"指定了承载方法名的参数,第9行中,我们还配置了一个调用FileAction不同方法的Action出口。

FileAction共有3个请求响应的方法,它们分别是:

·upload(…):处理上传文件的请求。

·listAllFile(…):处理加载数据库表中所有记录的请求。

·download(…):处理下载文件的请求。

下面我们分别对这3个请求处理方法进行讲解。

2.1 上传文件

上传文件的请求处理方法非常简单,简之言之,就是从Spring容器中获取业务层处理类FileService,调用其save(FileActionForm form)方法上传文件,如下所示:

由于FileAction其它两个请求处理方法也需要从Spring容器中获取FileService实例,所以我们特别提供了一个getFileService()方法(第15~21行)。重构的一条原则就是:"发现代码中有重复的表达式,将其提取为一个变量;发现类中有重复的代码段,将其提取为一个方法;发现不同类中有相同的方法,将其提取为一个类"。在真实的系统中,往往拥有多个Action和多个Service类,这时一个比较好的设置思路是,提供一个获取所有Service实现对象的工具类,这样就可以将Spring 的Service配置信息屏蔽在一个类中,否则Service的配置名字散落在程序各处,维护性是很差的。

2.2 列出所有已经上传的文件

listAllFile方法调用Servie层方法加载T_FILE表中所有记录,并将其保存在Request 域中,然后forward到列表页面中:

file-list.jsp页面使用Struts标签展示出保存在Request域中的记录:

展现页面的每条记录挂接着一个链接地址,形如:

fileAction.do?method=download&fileId=xxx,method参数指定了这个请求由FileAction 的download方法来响应,fileId指定了记录的主键。

由于在FileActionForm中,我们定义了fileId的属性,所以在download响应方法中,我们将可以从FileActionForm中取得fileId的值。这里涉及到一个处理多个请求Action 所对应的ActionForm的设计问题,由于原来的Action只能对应一个请求,那么原来的ActionForm非常简单,它仅需要将这个请求的参数项作为其属性就可以了,但现在一个Action对应多个请求,每个请求所对应的参数项是不一样的,此时的ActionForm的属性就

相关文档