[評論]
作者:陳小衝 (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;
}
public String getPassword() {
return password;
}
//用戶類型
public void setActortype(int actortype)
{
this.actortype = actortype;
}
public int getActortype()
{
return actortype;
}
}
//2.2 LoginAction.java(控制登錄流程的走向)
package com.chenxc.coursesonline.struts;
/**
* Title: LoginAction
* Description: 登錄驗證
* Time: 2004-3-20
* Company:
* Author: chenxc
* version 1.0
*/
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import org.apache.struts.action.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import com.chenxc.coursesonline.struts.*;
public class LoginAction extends org.apache.struts.action.Action{
public ActionForward execute(ActionMapping mapping,ActionForm actionForm,HttpServletRequest request,HttpServletResponse response) throws Exception
{
ActionErrors errors = new ActionErrors();
HttpSession session = request.getSession();
ActionForward forward = null;
LoginForm loginForm = (LoginForm)actionForm;
FacadeBean facadeBean = new FacadeBean();
if(facadeBean.actorlogin(loginForm.getUsername(),loginForm.getPassword(),loginForm.getActortype()))
{
forward = mapping.findForward("success");
return forward;
}
return (new ActionForward(mapping.getInput()));
}
}
//2.3 FacadeBean.java(實現登錄驗證)
package com.chenxc.coursesonline.struts;
/**
* Title: FacadeBean
* Description: 負責與Session Bean溝通
* Time: 2004-3-20
* Company:
* Author: chenxc
* version 1.0
*/
import com.chenxc.coursesonline.ejb20.*;
import javax.naming.*;
import java.util.Properties;
import javax.rmi.PortableRemoteObject;
import javax.ejb.CreateException;
import java.rmi.RemoteException;
public class FacadeBean
extends Object {
private static final String ERROR_NULL_REMOTE = "Remote interface reference is null. It must be created by calling one of the Home interface methods first.";
private static final int MAX_OUTPUT_LINE_LENGTH = 100;
private CoursesFacadeHome coursesFacadeHome = null;
private CoursesFacade coursesFacade = null;
//Construct the FacadeBean
public FacadeBean() {
initialize();
}
public void initialize() {
try {
//get naming context
Context context = getInitialContext();
//look up jndi name
Object ref = context.lookup("CoursesFacade");
//look up jndi name and cast to Home interface
coursesFacadeHome = (CoursesFacadeHome) PortableRemoteObject.narrow(ref,
CoursesFacadeHome.class);
}
catch (Exception e) {
e.printStackTrace();
}
}
private Context getInitialContext() throws Exception {
String url = "t3://192.168.100.134:7001";
String user = null;
String password = null;
Properties properties = null;
try {
properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
properties.put(Context.PROVIDER_URL, url);
if (user != null) {
properties.put(Context.SECURITY_PRINCIPAL, user);
properties.put(Context.SECURITY_CREDENTIALS,
password == null ? "" : password);
}
return new InitialContext(properties);
}
catch (Exception e) {
throw e;
}
}
//actorlogin
public boolean actorlogin(String username, String password, int actortype) {
try {
coursesFacade = coursesFacadeHome.create();
if (coursesFacade.actorLogin(username, password, actortype)) {
return true;
}
else
{
return false;
}
}
catch (CreateException ex) {
ex.printStackTrace();
}
catch (RemoteException ex) {
ex.printStackTrace();
}
return false;
}
}
/*---------------------------------------*/
//3、Session Bean程序
//3.1 SessionFacadeHome.java
package com.chenxc.coursesonline.ejb20;
import javax.ejb.*;
import java.util.*;
import java.rmi.*;
public interface SessionFacadeHome extends javax.ejb.EJBHome {
public SessionFacade create() throws CreateException, RemoteException;
}
//3.2 SessionFacade.java
package com.chenxc.coursesonline.ejb20;
import javax.ejb.*;
import java.util.*;
import java.rmi.*;
public interface SessionFacade extends javax.ejb.EJBObject {
public boolean actorLogin(String actor, String password, int actortype) throws RemoteException;
}
//3.2 SessionFacadeBean.java
package com.chenxc.coursesonline.ejb20;
import javax.ejb.*;
import javax.naming.*;
import java.rmi.RemoteException;
public class SessionFacadeBean implements SessionBean {
SessionContext sessionContext;
ActorHome actorHome;
Actor actor;
public void ejbCreate() throws CreateException {
try
{
Context ctx = new InitialContext();
actorHome = (ActorHome)ctx.lookup("Actor");
}
catch (Exception ex) {
throw new EJBException(ex);
}
}
public void ejbRemove() {
/**@todo Complete this method*/
}
public void ejbActivate() {
/**@todo Complete this method*/
}
public void ejbPassivate() {
/**@todo Complete this method*/
}
public void setSessionContext(SessionContext sessionContext) {
this.sessionContext = sessionContext;
}
public boolean actorLogin(String username, String password, int actortype) {
try
{
actor = actorHome.findByName(username,actortype);
if(actor.getPassword().equals(password))
{
System.out.println(username + " 登錄成功!");
return true;
}
}catch(ObjectNotFoundException ex) {
}
catch(Exception ex) {
ex.printStackTrace();
}
return false;
}
}
/*-------------------------------*/
//4、爲Entity Bean(Actor)新增一個findByName的Finder,如下圖
Finder名findByName,返回值Actor,輸入參數用戶名,用戶類型,接口local home,EJB-QL: SELECT OBJECT(a) FROM Actor AS a WHERE a.username = ?1 AND a.actortype = ?2
6.3.3 階段總結
Client -> Struts -> Session Bean -> Entity Bean,實現一個登錄流程用了十幾個java程序,真是累人的說。一個JSP程序就可以實現的功能卻要大陣戰對待,殺雞用牛刀乎?
答案是否定的,因爲EJB存在的意義在於分佈式計算和分層的思想。
6.4未完成的
寫到這裏,CoursesOnline系統只實現了系統登錄驗證的功能,主要的業務邏輯基本上沒實現,但MVC框架,Façade Design Pattern都或多或少有所描述,繼續完成CoursesOnline系統應該不會太困難。
另外由於使用CMP查詢記錄集時處理起來比較麻煩,所以在瀏覽課程或瀏覽學生等記錄集查詢時可以考慮用BMP來實現,或者乾脆在Session Bean實現。
附錄A 開發環境的配置
A.1 安裝Jbuilder 9
請參考Jbuilder 9的安裝手冊
A.2 安裝Oracle 9i
請參考Oracle 9i的安裝手冊
A.3 安裝WebLogic 7
請參考WebLogic 7的安裝手冊
A.4 創建WebLogic Domain
請參考WebLogic創建Domain的相關資料
A.5 配置Jbuilder 9
A.5.1選擇Tools->Configure Servers配置Server信息
Home Directory:選擇Weblogic安裝目錄下的Server目錄,如:C:eaweblogic700server
Main Class,VM Parameters:系統會自動獲得,不用修改
Working Directory:創建的新Domain目錄 如:C:eauser_projectsmydomain
Class 中:由於沒有Weblogic的SP包,刪除weblogic_sp.jar包,僅保留weblogic.jar
JDK Install directory:選擇安裝Weblogic目錄下的JDK目錄 如:D:eajdk131_03
BEA Home directory: 選擇安裝Weblogic目錄 如:C:ea
User Name and Password:創建Domain時Administor的用戶名稱和密碼
Domain Name and Server Name:系統會自動獲得
A.5.2配置缺省工程的Server信息(Project->Default Projects Properties)
這樣一來新建的工程默認情況下就使用這種Server配置
A.5.3配置數據庫驅動
點擊Add按鈕
在本機Oracle的目錄下選擇Oracle JDBC lib的路徑,如D:OracleOra92jdbclibclasses12.jar
配置數據庫驅動後需重啓JB才能使設置生效
附錄B 參考資料
1)《Mastering EJB 2》
2)《Enterprise JavaBeans, 3rd Edition》
3)《EJB Design Pattern》
4)http://java.sun.com
附錄C 建表及表初始化的sql腳本
C.1 建表
CREATE TABLE Appointment (ActorID SMALLINT NOT NULL,CoursesID SMALLINT NOT NULL);
ALTER TABLE Appointment ADD ( PRIMARY KEY (ActorID, CoursesID) ) ;
CREATE TABLE Courses (
CoursesID SMALLINT NOT NULL,
CoursesName VARCHAR(30) NULL,
StartDate DATE NULL,
EndDate DATE NULL,
RoomID SMALLINT NULL,
ActorID SMALLINT NULL
);
ALTER TABLE Courses ADD ( PRIMARY KEY (CoursesID) ) ;
CREATE TABLE ActorInfo (
ActorID SMALLINT NOT NULL,
ActorName VARCHAR(30) NULL,
Phone VARCHAR(16) NULL,
Email VARCHAR(50) NULL
);
ALTER TABLE ActorInfo ADD ( PRIMARY KEY (ActorID) ) ;
CREATE TABLE Actor (
ActorID SMALLINT NOT NULL,
UserName VARCHAR(20) NULL,
Password CHAR(8) NULL,
ActorType SMALLINT NULL
);
ALTER TABLE Actor ADD ( PRIMARY KEY (ActorID) ) ;
CREATE TABLE Room (RoomID SMALLINT NOT NULL,RoomName VARCHAR(20) NULL);
ALTER TABLE Room ADD ( PRIMARY KEY (RoomID) ) ;
ALTER TABLE Appointment ADD ( FOREIGN KEY (CoursesID) REFERENCES Courses ) ;
ALTER TABLE Appointment ADD ( FOREIGN KEY (ActorID) REFERENCES Actor ) ;
ALTER TABLE Courses ADD ( FOREIGN KEY (ActorID) REFERENCES Actor ) ;
ALTER TABLE Courses ADD ( FOREIGN KEY (RoomID) REFERENCES Room ) ;
ALTER TABLE ActorInfo ADD ( FOREIGN KEY (ActorID) REFERENCES Actor ) ;
C.2表的初始化
--1)初始化系統管理員
insert into actor values(1,sysadmin,sysadmin,0);
insert into actorinfo values(1,’sysadmin’,66668888,[email protected]);
--2)初始化老師
--增加任我行老師
insert into actor values(2,’rwx’,’rwx’,1);
insert into actorinfo values(2,’任我行’,’77778888’,’[email protected]’);
--增加東方不敗老師
insert into actor values(3,’dfbb’,’dfbb’,1);
insert into actorinfo values(3,’東方不敗’,’88888888’,’[email protected]’);
--增加嶽不羣老師
insert into actor values(4,’ybq’,’ybq’,1);
insert into actorinfo values(4,’嶽不羣’,’99998888’,’[email protected]’);
--3)初始化教室
insert into room values(1,’黑木崖教室1’);
insert into room values(2,’黑木崖教室2’);
insert into room values(3,’華山教室3’);
附錄D 關於作者
姓名:陳小衝
暱稱(筆名):疾風之鷹
簡介:2001年6月畢業於北京化工大學計算機專業,畢業後一直在創原天地從事J2EE開發工作。