孙 璐
MVC设计模式被广泛应用于 B/S架构的应用系统开发,采用MVC设计模式,我们可以将表现层、业务层分离,构建低耦合度的应用。采用Java开发Web应用可以采用几种符合 MVC 设计模式的开发手段,例如JSP(V)-Servlet(C)-JavaBean/EJB(M),或者JSP(V)-JavaBean(C)-JavaBean/EJB(M)等,每种开发手段各有优劣,具体使用要看使用者的习惯及项目的需求特点[1]。不管何种开发方式,表现层都是采用JSP实现。与JavaBean、Servlet、EJB等纯采用Java开发的程序不同,混合了HTML和Java语言(在HTML中会夹杂着Java语言或JSP标记),部署在JSP服务器后再由服务器动态编译为Servlet程序执行,原始的 JSP程序的逻辑相对于纯 Java的 JavaBean、Servlet、EJB而言,要混乱的多,是比较容易出错的地方,也是程序调试的难点。如果是实现一般功能的JSP程序,问题还不大,但是涉及到数据库应用,就比较复杂了,尤其是查询类的程序,如果 JSP程序中出现异常,很有可能使得JSP中引用的数据连接没有关闭,将直接影响后端的数据库服务器,严重者将使得数据库服务器因会话溢出而崩溃[2]。
之所以可能出现这类问题,原因是大多数使用者在开发JSP程序时,喜欢使用面向连接的行集对象ResultSet,面向连接的行集对象在JSP容器(JSP Container)不缓存数据集的内容,数据集的内容缓存在数据库服务器的相应会话(Session)中,但是使用时,需要一直保持数据库连接处于活动(Alive)状态,一旦数据库连接关闭,该行集就不能再使用。而在JSP中,如果使用行集对象,则要保证无论在JSP中出现何种预知或无法预知的结果(包括错误),该JSP程序必须能关闭数据连接,这对一些比较复杂的JSP程序而言,相当不容易。而本文所述的非连接行集对象CacheRowSet可以在短暂连接数据源并获取数据后,脱离数据源而使用(即可以立刻关闭数据连接),解决了这类矛盾。
为什么基于Java的B/S数据库应用中,当数据库连接没有关闭时,会出现上节提到的数据库会话溢出的问题呢?Java虚拟机不是有自动垃圾回收(Garbage Collection)的功能吗?要想回答这个问题,我们需要对Java及JDBC作一个简介。
Java是Sun公司推出的Java程序设计语言和Java平台的总称。Java语言是一种面向对象的、解释型的、多线程的动态语言,与传统的应用程序不同,Java程序采用字节码格式,独立于操作系统及硬件平台,运行于专用的 Java平台之中,良好的可移植性使得其得到了迅速的普及。
Java平台由 Java虚拟机(JVM)和 Java 应用编程接口(Java API)构成。Java虚拟机为Java程序提供屏蔽了操作系统与硬件差异的虚拟计算机运行环境,在它的隔离下,Java程序不需要从操作系统或硬件平台上直接获取资源(如内存、寄存器等),而改由使用JVM提供的资源(字节码指令集、JVM寄存器、栈、垃圾回收堆等),Java对象所需的存储空间是在垃圾回收堆上分配的,垃圾回收堆为 Java程序中的对象分配内存空间,管理内存的使用,并在对象使用完毕,不再被程序引用时,自动将其回收到堆中,Java程序不需要也无法自己控制内存的使用。Java API为Java程序提供了一个独立于操作系统的标准编程接口,它本身是使用Java语言编写的,包括了很多标准的预定义的类库,用于支持完成某些功能,如GUI设计、I/O、网络编程、数据库操作等,可以极大的提高Java程序的开发效率。Java API已经从1.1版发展到1.6版[3,4]。
JDBC(Java Database Connectivity)即是Java API中的一组包(Package),提供连接各种关系数据库的统一接口,可以为多种关系数据库提供统一访问,它由一组用 Java语言编写的类和接口组成。其中,包java.sql由JDK1.x开始提供,包括了Connection(数据库连接)、CallableStatement(存储过程调用)、PreparedStatement(SQL命令调用)、ResultSet(面向连接的行集)等接口,包 javax.sql、javax.sql.rowset由JDK1.4开始提供(即JDBC3.0以上),包括 DataSource(数据源连接)、RowSet(添加了 JavaBeans属性)、CachedRowSet(非面向连接行集)、WebRowSet(RowSet的XML文档格式)等接口[5]。以查询应用为例,如果使用普通的行集对象,用JDBC开发的流程一般如下:
1) 创建数据库连接对象,建立数据库连接,有异常转步骤5;
2) 构建查询SQL语句,有异常转步骤5;
3) 执行语句,创建行集对象,有异常转步骤5;
4) 遍历行集对象,显示数据,有异常转步骤5;
5) 关闭数据库连接,释放连接对象;
可以看出,程序要求任何时候都必须通过主动关闭数据库来释放连接对象,而不是指望 Java的垃圾自动回收机制起作用,为什么会这样呢?并不是 Java的垃圾回收机制在这种情况下不起作用了,而是 Java的垃圾回收机制,仅会回收连接对象本身(即释放了JVM宿主的内存资源),而不能释放数据库服务器上的连接会话(Connection Sessions)资源。
由于JDBC与数据库服务器间通过TCP/IP方式通讯,一旦连接,数据库服务器会为该连接创建一个专门的会话(session)以管理该连接,每个会话拥有数据库服务器分配的一定资源(如内存等),用于该会话所对应的TCP连接上的各项任务执行,如果客户机不再需要使用一个连接,必须显式地关闭连接以通知数据库服务器回收会话资源,否则,在数据库服务器上将出现许多处于空闲(Idle)状态的会话,占用数据库服务器的内存资源,并影响数据库服务器的性能,更有甚者,这种空闲会话除非数据库服务器重启,否则不会自行回收,一旦达到上限,数据库服务器势必因内存耗尽而无法使用,这就是引言中所指的“会话溢出”。
当然,在程序结构严谨的纯 Java的程序中,如 Java Application、Applet、Servlet、JavaBean、EJB等,管理好数据库连接,在所有可能的程序出口显式关闭数据库连接并不是什么难事,因此使用面向连接的行集对象并无不可,还可以减小应用服务器的负担,但在混合了 HTML、Java、JSP标记、Javascript等的JSP程序中,要做到这一点就不太容易了,页面越复杂,打开的行集数目越多,显示的数据内容越分散,页面中的分支越多,越是容易出现问题,而且当页面程序数量比较大时(一个B/S应用系统,JSP页面程序经常数以百计),非常难定位发生问题的JSP程序。因为一些造成数据库连接没有关闭而结束的 JSP程序并不会在用户端出现错误信息,因此使用者往往无法察觉到这类问题,而数据库会话溢出也需要很长时间才会发生,这主要取决于发生问题的JSP程序代码被调用的次数、数据库服务器的内存以及B/S系统是否需要长期连续运行,有时候是几天,有时候可能会是几周,对于不需要连续运行的B/S系统,这种现象可能永远也不会出现。而一旦发生了“会话溢出”,除了重启数据库服务器以外,别无它法,这将暂时中止应用系统的服务,对于提供公众服务的B/S应用系统而言,将是个灾难。
使用 JDBC3.0中提供的非连接行集接口,可以事半功倍地解决上述问题,下面我们以一个例子展示一下两种不同行集接口的使用区别。
为了方便表述,我们假设要开发一个学生学籍管理系统,其中有一项功能是根据查询条件,查询符合条件的学生资料的页面,数据库服务器及数据结构如表1、2所示:
表1 数据库服务器信息
表2 表tblStudent数据结构
功能要求:可以根据学生序号、学生学号或姓名模糊查询学生资料,查询结果以列表方式展示。
如前文所述,基于Java的B/S应用采用MVC设计模式开发有多种实施手段,为了简化设计,本例采用JSP(View)-JavaBean(Modulel)-JavaBean(Control)方式开发,表3是本例中用到的各程序的说明:
表3 程序说明
因文章篇幅所限,下面仅概略展示本例所涉及程序的方法说明、主要代码及设计的重要部分。
JdbcDriver.java用于创建及释放Oracle数据库连接,主要方法包括两个[6]:
1) Connection CreateOracleConnection():创建 Oracle数据库连接并返回Connection对象。
2) boolean FreeConnection(Connection connection):释放连接。
查询条件程序ConditionBean.java是一个符合JavaBean规范的 Java程序,本例可作为查询条件的包括学生序号、学生代码、学生姓名,因此对于每个条件需要提供一对set/get方法,如以学生姓名为例,代码如下:
返回结果程序 ResultBean.java也是一个符合 JavaBean规范的 Java程序,本例中因为要比较两种不同行集对象的应用区别,因此ResultBean.java包含两个方法分别输出两种行集结果,代码如下[7]:
查询程序的主要逻辑如下:
1) 创建数据库连接;
2) 接收查询条件,构建SQL语句;
3) 执行SQL语句,获取行集对象;
4) 如果是非连接行集对象,可以释放数据库连接并返回行集对象,否则直接返回行集对象;
主要代码如下:
注意上面代码中的粗斜体部分。
查询结果的显示在JSP程序中处理,对于使用连接行集而言,需要在页面的处理过程中,保持数据库连接处于打开状态,一直到离开页面才可以以显式方式释放数据库连接,而对于非连接行集就不需要这个过程了,这样,程序的处理
逻辑更清晰。因篇幅所限,下面仅给出使用连接行集时的代码,注意粗斜体部分的连接释放处理。
本文以一个实例展示了非连接行集在 B/S系统开发中的应用,事实上,非连接行集比连接行集更适合数据库查询一类的应用的开发,且系统具有开发效率高、维护成本低的优点,本例的设计思想,被笔者在许多应用项目中都曾经使用过,效果非常不错,具有良好的应用前景。
[1] 袁梅冷,黄烟波,黄家林,翁艳彬. J2EE应用模型中 MVC软件体系结构的研究与应用[J] . 计算机应用研究,2003,( 3):147-149.
[2] Nourie D. Java Technologies for Web Applications[EB/OL] .Http://java.sun.com/developer/technicalArticles/t ools/webapps_1/, (2006-11-15).
[3] SUN Co. The Java Language Specification, Third Edition[EB/OL] .Http://java.sun.com/docs/books/jls/third_edition/html/j3TOC.html, 2005.
[4] SUN Co. Java 2 Platform, Standard Edition, v1.4.2 API specification [EB/OL] . Http://java.sun.com/j2se/1.4.2/docs/api/, 2003.
[5] SUN Co. Getting Started with the JDBC API[EB/OL] .Http://java.sun.com/j2se/1.5.0/docs/guide/jdbc/getstart/Ge ttingStartedTOC.fm.html, 2003.
[6] Oracle Co. Oracle9i Java Developer's Guide, Release 2[M/CD] . Oracle Co. March 2002.
[7] (美)霍斯特曼. JAVA核心技术卷 II:高级特性 [M] .陈昊鹏,等译.北京:机械工业出版社,2008: 202-256.