作者:陈小冲 (dev2dev id:chenxc)
一、CoursesOnline简介
CoursesOnline是一个实验性质的系统,CoursesOnline是“在线课程”的意思,在这个CoursesOnline系统里,学生选择课程,老师可以开设课程,系统管理员则对学生、老师以及课程进行管理。
CoursesOnline使用J2EE来实现,目的是为其它EJB系统的开发提供一些参考。
二、开发环境
CoursesOnline使用Jbuilder9 + Oracle9i + WebLogic7的开发环境。
因为J2EE是一种行业标准,所以采用哪种开发环境的搭配并不是最重要的。目前其它常见开发环境的搭配还有Eclipse + MySQL + Jboss(都是OpenSource),VJA + DB2 + WebSphere,等等。
Jbuilder9 + Oracle9i + WebLogic 7开发环境的配置请参考附录A。
三、CoursesOnline需求分析
3.1 CoursesOnline用例
上图是CourcesOnline的Use Case Diagram。显而易见,系统中有学生、老师和系统管理员三种Actor。学生需要注册成系统用户后才能浏览课程和选课。
3.2 数据库建模(ER图)及数据字典
上图是CoursesOnline的数据库ER图,建模工具是ERWin。顺便提一下,ERWin的正向工程支持ER图直接生成数据库表结构,逆向工程支持数据库表结构生成ER图。
CoursesOnline使用到的表有5个,数据字典如下:
3.2.1 Actor登录信息表(Actor)
序号 | 字段名 | 数据类型 | 约束 | 备注 |
1 | ActorID | SmallInt | PK | Actor标识符 |
2 | UserName | Varchar(20) | Actor登录帐号 | |
3 | Password | Char(8) | Actor登录口令 | |
4 | ActorType | SmallInt | Actor类型,0:系统管理员;1:老师;2:学生 |
3.2.2 Actor基本信息表(ActorInfo)
序号 | 字段名 | 数据类型 | 约束 | 备注 |
1 | ActorID | SmallInt | FK(Actor) | Actor标识符 |
2 | ActorName | Varchar(20) | Actor姓名 | |
3 | Phone | Varchar(16) | 电话 | |
4 | | Varchar(50) | |
3.2.3 课程信息表(Courses)
序号 | 字段名 | 数据类型 | 约束 | 备注 |
1 | CoursesID | SmallInt | PK | 课程标识符 |
2 | CoursesName | Varchar(20) | 课程名称 | |
3 | StartDate | Date | 课程开始时间 | |
4 | EndDate | Date | 课程结束 | |
5 | ActorID | SmallInt | FK(Actor) | |
6 | RoomID | SmallInt | FK(Room) | 教室标识符 |
3.2.4 学生选课表(Appointment)
序号 | 字段名 | 数据类型 | 约束 | 备注 |
1 | ActorID | SmallInt | FK(Actor) | Actor标识符(学生,ActorType=2) |
2 | CoursesID | SmallInt | FK(Courses) | 课程标识符 |
3.2.5 教室信息表(Room)
序号 | 字段名 | 数据类型 | 约束 | 备注 |
1 | RoomID | SmallInt | PK | 教室标识符 |
2 | RoomName | Varchar(30) | 教室名称 |
四、CoursesOnline系统设计
4.1 Design Pattern的选择和思考
在系统设计上,Design Pattern的选择是很重要的。因为正确的Design Pattern不仅在开发阶段可以让开发人员思路清晰得心应手,而且在维护阶段也不至于让维护人员抓狂。除此之外,对于系统的健壮和运行效率而言也起着举足轻重的作用。如果采用了错误的Design Pattern,那么对于系统来说就像是在错误的时间错误的地点与错误的敌人打了一场错误的战争。
下面以CoursesOnline系统学生注册时采用的两种不同的处理方法为例来简单说明选择Design Pattern的重要性。
学生在Client提交注册信息,包括登录帐号,登录口令,姓名,电话和Email五项内容。从数据库ER图中我们可以看到学生信息被分散在两张表里,也就是说服务器端有两个Entity Bean来存取学生的注册信息,一个为Actor,另一个为ActorInfo。
方法一:客户端直接与Entity Bean沟通以完成工作
方法二:客户端与Session Bean交互,由Session Bean与Entity Bean沟通以完成工作
方法一的设计虽然也能完成对学生注册的处理,但是客户端必须写所有的业务逻辑代码,而且由于客户端直接访问Entity Bean,不但造成了多次的网络roundtrip,使执行效率大幅下降,也使客户端与Entity Bean形成强耦合,日后不管修改客户端还是Entity Bean,都会牵一发而动全身,对系统的改造简直就是一场灾难。
方法二不但大幅降低了网络的roundtrip,而且Session Façade分隔了客户端和Entity Bean,Entity Bean对于客户端来说是透明的,客户端需要关心的只是Session Façade提供的接口。这样一来系统的可扩展性就得到了质的提升。
4.2 CoursesOnline系统示意图
五、创建数据库
先有鸡再有蛋?还是先有蛋再有鸡?
先有数据库表再有Entity Bean?还是先有Entity Bean再有数据库表?
这两个问题有异曲同工之妙。鸡与蛋的问题已经讨论几千年了,哲学的Big Fans可能会争的脸红耳赤唾沫横飞;偶们只是普通的程序员,谁先谁后的问题还留给理论学家吧。
可能会有人先设计Entity Bean再建数据库表,也有可能反其道而行。在CoursesOnline这个实验性质系统的开发过程中,是先建数据库表然后才有Entity Bean。
5.1 新建一个数据库
Oracle可以在命令行模式下敲入dbca,或者直接在开始菜单里找到并运行Database Configuration Assistant,然后根据向导的提示新建一个名为CoursesDB的数据库。
Oracle 9i新建数据库的详细过程请参考Oracle的相关文档。
5.2 为数据库创建一个用户
在命令行下敲入oemapp console,或者直接在开始菜单里找到EnterPrise Manager Console打开Oracle管理控制中心,以SYSDBA的身份进入CoursesDB数据库后,在安全性->用户中新增一个用户,如chenxc,口令chenxc,并赋予dba的角色。这些设置在接下来的CourseseOnline系统具体开发中会用到。
Oracle 9i新建用户的具体操作请参考Oracle的相关文档。
5.3 建表及表的初始化
5.3.1 建表
建表的方法有N种:1)用sqlplus连上CoursesDB数据库后,用sql语句把数据字典中列出来的表结构敲进去;2)把数据字典中表结构写成sql脚本文件,然后用sqlplus连上CoursesDB,执行SQL>@@ c:CoursesDB.sql;3)使用一种支持正向工程的数据库建模工具直接把ER图转换成数据库表结构,如ERWin;4)使用可视化工具建表,如PLSQL Developer。
其它的我知道的还有我还不知道的数据库的建表方法,在这里就不一一列举了,有兴趣的请自行研究,然后把经验告诉大家。
附录C提供了CoursesOnline建表及表的初始化的sql脚本。
5.3.2 表的初始化
从CoursesOnline用例图中可以看出,系统没有提供系统管理员、老师以及教室的管理的接口,系统管理员、老师和教室的信息在数据库建完后就应该初始化了。也就是说,这些信息是已经存在的,除非直接操作数据库,否则无法改变系统管理员、老师和教室的信息。
建完表后,我们为CoursesOnline系统初始化了一位系统管理员,名为sysadmin;三位老师,分别为任我行、东方不败和岳不群;除此之外还初始化了三间教室,分别为黑木崖教室1、黑木崖教室2和华山教室3。
附录C提供了CoursesOnline建表及表的初始化的sql脚本。
六、CoursesOnline的具体实现
6.1 在Jbuilder 9中新建一个工程
启动Jbuilder 9,File->New Project,项目名为CoursesOnline,选择路径后,点击finish
因为CoursesOnline项目使用Oracle 9i数据库,所以还需要把Oracle的驱动加载进来,Project -> Project Properties -> Path -> Required libraries,点击add,在Select One Or More Libraries窗口中选择OracleJDBCLib
OracleJDBCLib的配置请参考附录A.5.3 配置数据库驱动
6.2 创建Entity Bean(CMP)
6.2.1 新建一个EJB Module
File -> New -> Enterprise ->EJB Module
点击ok,并在接下来的窗口中为EJB Module命名为Courses,结果如下图
6.2.2 Import Schema From Database
在上图中的DataSources上点击右键,或者在Courses设计面板上点击右键,然后点击Import Schema From Database,在弹出的窗口中输入Driver,URL等参数,如下图所示
点击ok,这时数据库中的5个表在DataSources面板中显示出来,如下图所示
6.2.3 创建Entity Bean
在上图中DataSources面板中右键Actor,在弹出菜单中选择Create CMP 2.0 Entity Bean,结果如下图
左键点击Entity Bean Actor可以在弹出的窗口中编辑它的属性,在这里我们暂时使用默认值。接下来用同样的方法为其它四个表创建CMP 2.0 Entity Bean,结果如下图
从左边的Project下拉菜单中可以看到,每个Entity Bean都有三个(或四个)Java程序与之相对应,例如Actor.java、ActorBean.java和ActorHome.java。为了便于管理,以及避免和后面的程序胡搅蛮缠搞得眼睛高度紧张,我把Entity Bean对应的java程序都放在com.chenxc.coursesOnline.ejb20下。点击Entity Bean的名称,在Bean Properties窗口中点击Classes And Packages…按钮,然后为每个类指定路径。
6.2.4 阶段总结
这一节中创建了五个CMP 2.0 Entity Bean,是CMP而不是BMP,是2.0而不是1.x,是local接口而不是remote接口,而且还没有动手写过任何代码。本文并不打算深入探讨EJB的内部机制,而仅仅是举例子来说明使用EJB实现分层思想及分布式计算的方法。如果要研究EJB的细节,请参考附录B 参考资料列举的书籍或文章。
当前阶段的五个Entity Bean只能算是半成品,要完成业务逻辑还需要进一步加工,这些在接下来开发Session Bean的过程中会提到。欲知后事如何,请听下回分解。
6.3 创建Session Bean(Stateless)
6.3.1 SessionFacade ? 连接Struts与Entity Bean的桥梁
新建一个名为SessionFacade的Session Bean。SessionFacade负责与Entity Bean沟通,并为Struts提供接口。
这个Session Bean扮演一个中间人的角色,就像是北京西客站的售票厅,旅客可以买T97的火车票去往广州,也可以买T31的火车票去往上海。在CoursesOnline系统中,Struts跟购买火车票的旅客一样,通过SessionFacade提供的接口完成系统登录、学生注册、学生选课等操作。
下面以Actor登录为例来说明SessionFacade是怎样扮演中间人的角色,如下图:
在SessionFacade中新增一个名为actorLogin的方法,返回值boolean,输入参数用户名,口令和用户类型(学生、老师或系统管理员),接口remote。
方法actorLogin提供给远程的Struts调用。
登录处理流程如下:
1、用户在页面上输入用户名、口令和用户类型并提交;
2、JavaBean存储用户在页面提交上来的用户名、口令和用户类型;
3、Struts读取JavaBean的用户名、口令和用户类型;
4、Struts通过远程接口调用SessionFacade的actorLogin(String username,String Password,int actortype)方法;
5、SessionFacade通过本地接口调用Entity Bean(Actor)的findByName(String username,int actortype)方法;
6、SessionFacade获取Entity Bean(Actor)的findByName(String username,int actortype)方法的返回值;
7、SessionFacade在actorLogin(String username,String Password,int actortype)方法中比较Entity Bean(Actor)的findByName(String username,int actortype)得到的password的值;
8、如果两个password的值相等,则actorLogin(String username,String Password,int actortype)返回true,否则返回false;
9、Struts根据SessionFacade的actorLogin(String username,String Password,int actortype)的返回值(true or false)判断登录是否成功,并导航到对应的页面。
6.3.2 登录流程的代码
/*----------------------------------------------------*/
//1、用户登录界面 - login.jsp
<!- 部分HTML代码此处省略 -- >
<form name="teaForm" method="post" action="<%=contextPath%>/login.do">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="60"><font size="2">用户类型</font></td>
<td>
<input type="radio" value="0" name="actortype" checked><font size="2">系统管理员 </font>
<input type="radio" value="1" name="actortype"><font size="2">学生 </font>
<input type="radio" value="2" name="actortype"><font size="2">老师 </font>
</td>
</tr>
<tr>
<td width="60"><font size="2">用户名</font></td>
<td><input name="username" value="" size="12"> *</td>
</tr>
<tr>
<td width="60"><font size="2">口令</font></td>
<td><input type="password" name="password" value="" size="8" maxlength="8"> *</td>
</tr>
</table>
<br>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="50"> </td>
<td><input type="submit" value="登 录"></td>
</tr>
</table>
</form>
/*----------------------------------------------*/
//2、Struts程序
//2.1 LoginForm.java(存储用户在页面上提交的用户名、口令和用户类型)
package com.chenxc.coursesonline.struts;
/**
* Title: LoginForm
* Description: 存储登录信息
* Time: 2004-3-20
* Company:
* Author: chenxc
* version 1.0
*/
import java.util.*;
public class LoginForm extends org.apache.struts.action.ActionForm{
private String username; //用户名
private String password; //口令
private int actortype; //用户类型
//用户名
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
//口令
public void setPassword(String password) {
this.password = password;
}