第四章

指南:建立Enhydra 应用程序

这一章描述了怎样从点滴开始建立Enhydra 应用程序,并且提供了开发Enhydra应用程序的重要技巧

在这个指南中你将会学到

如果你对enhydra已经非常熟悉,你可以直接跳到第五章DiscRack 应用程序 ,你将会看到一个具有更多高级特性的应用程序

注意:

在这个指南中,你需要经常输入命令,在UNIX(linux)平台下,你可以在任何shell下输入,在windows系统下,你必须在Cygwin的shell窗口下输入

创建你的第一个应用程序


使用enhydra 应用程序向导(application appwizard)是建立enhydra应用程序的一个捷径,应用程序向导为新的应用程序建立基本的java文件和目录结构,在这份指南中,你将学会使用图形界面的应用程序向导来创建一个简单的应用程序

1. 创建一个目录,例如 mkdir myapps
2.打开一个shell窗口,进入这个目录 例如 cd myapps
3.输入 appwizard (不带参数)来启动图形界面的应用程序向导,应用程序向导可以产生两种截然不同的enhydra项目: Web Application和Enhydra super-servlet application,在这个指南中我们产生的是第二种

注意:

如果应用程序向导没有启动,可能是环境变量没有设置正确,你可以参考lutris enhydra的安装指南

下面这个URL有html格式的安装指南

http://www.lutris.com/documentation/index.html.


图形4.1应用程序向导(Application Wizard GUI )

注意:

随着Lutris enhydra 3.5的发布,应用程序向导有了较大的变化,以前,应用程序向导是一个命令行工具,只能通过输入 newapp并且加上参数来建立一个项目,但是这一切已经改变了,目前的图形化的应用程序向导可以更好的产生应用程序的基本框架(包含文件及目录结构)

4:使用应用程序向导产生一个简单的应用程序

1:选择产生何种应用程序

在Genetator右边的下拉菜单中选择Enhydra superservlet,单击下一步

2:指定客户程序类型(client type)和目录信息

接受默认的客户程序类型:html(还有一种是Wml),在Project directory 中输入simpleApp ,在Package中输入simpleapp(注意大小写),在Root path 中输入/enhydra/myapps.单击下一步

3:指定版权信息

单击下一步接受默认信息(没有版权),单击下一步

4:指定产生何种补充的文件

选择Create Makefiles Create Shell Scripts ,单击完成

应用程序向导会产生一个目录simpleApp,这就是应用程序的根目录

5:使应用程序根目录成为当前目录

cd simpleApp

6:浏览应用程序根目录你会发现应用程序向导产生了下列文件

应用程序根目录的内容在下面会有详细解释

下一节你将完成建立你的第一个应用程序

编译应用程序


1:在shell窗口下进入应用程序根目录,输入make来编译应用程序
cd /enhydra/myapps/simpleApp
make

这将在应用程序根目录下创建两个子目录

应用程序根目录下的make文件: Makefile,包含了一些指令,这些指令告诉 make 递归地下溯应用程序目录树,每个子目录中的Makefile文件也遵从同样的规则,同时这个文件有一个include指令:include $(ROOT)/config.mk,这个文件主要引用<enhydra_root>/lib/stdrules.mk,stdrules.mk是所有enhydra应用程序都要用到的一个make 文件

当你编译应用程序时,make将编译simpleApp 源文件目录下的文件(simpleApp/src),同时在classes目录中创建相应的目录结构,然后将class文件打包成jar文件,放到output目录,同时放到output目录下的还有运行应用程序所需的配置文件

2.在shell窗口下输入下列命令启动应用程序:
cd output
./start


注意: Multiserver 管理控制台提供一个图形界面来管理应用程序. 你可以通过他启动和停止应用程序. 可以参考"Launching the Admin Console" 这一节来得到更多的信息和用法指导


3.为了能访问应用程序,你需要在浏览器中输入下面的url
http://localhost:9000


浏览器将会显示simpleApp 应用程序的欢迎界面

图形4.2 simpleApp 应用程序的欢迎界面

4.在shell窗口下 按下CTRL + C键,应用程序将会中止

工作原理


应用程序向导产生的应用程序提供了一个简单的描述enhydra工作原理的示例
看一下myapps/simpleApp/src/simpleapp/presentation/Welcome.html,这个文件包含很多html标记. 注意下面这些标记:
<center>
The time at the web server is:
<span id="time">1/1/00 00:00:00 (static)</span>.
</center>


运行时,enhydra将用一个真实的日期取代<SPAN>标记中的内容. ID 属性中的文本(本例中即time)只是一个占位符; 运行时他将不会出现. 而<SPAN> 标记外的句号将不会被取代. 这样这一句总会以句号结尾
看一下统一目录下的WelcomePresentation.java 文件. 尤其注意下面的代码:
WelcomeHTML welcome;
String now;
...
welcome = (WelcomeHTML)comms.xmlcFactory.create(WelcomeHTML.class);
now = DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());
welcome.getElementTime().getFirstChild().setNodeValue(now);


这段代码用来取代<span>标记内的日期.
前两句建立了WelcomeHTML类的一个变量welcome和String类的一个变量now,. 下一行中的xmlcFactory 将你的HTML 也面实例化. 紧接着变量now被设置为当前日期并被格式化. 最后一行设置welcome类中的time 元素值为now.

当你编译应用程序时,xmlc(Extensible Markup Language Compiler )将会找到html文件中的<SPAN>标记
,并且识别出ID 属性和其值"time". xmlc将会创建一个java类WelcomeHTML, 这各类将会有一个方法getElementTime(). 应用程序通过getElementTime() 来修改<SPAN> 标记中的内容.
注意:通常, XMLC 将会为每个带id属性(值为xxx)的span标记创建getElementxxx() 方法.方法名中的 xxx 为span标记内id属性值的大写拚法(即time变为Time,首字母大写).

运行时,应用程序将用当前日期取代<span>内的原始内容,然后写入到http响应的文档中去,
看起来如下
...
<CENTER>
The time at the web server is: <SPAN>10:40 AM</SPAN>.
</CENTER>
...

想得到更详细的关于xmlc的信息请看"Using XMLC." 这一节,想得到关于应用程序向导(Enhydra Application Wizard)的更多信息,请看"Enhydra Application Wizard."

SimpleApp中的目录和文件


让我们更详细的看一下simpleApp目录中的文件和目录:


最终的应用程序包含一个JAR 文件: simpleApp.jar, 和应用程序配置文件:simpleApp.conf. make 程序同时把Multiserver 的配置文件:servlet.conf也复制过来, output目录中的start脚本使得启动应用程序更容易
配置文件

用程序的配置文件包含了一些关键信息,这些信息决定了应用程序怎样运行,这些文件包括:

Multiserver 配置文件, servlet.conf, 位于servlet子目录

应用程序配置文件, <AppName>.conf (例如, simpleApp.conf)

注意 管理控制台( Admin Console)是运行在Multiserver上的一个图形化的程序 .管理控制台有其自己的配置文件: multiserverAdmin.conf. 而且, 管理控制台管理的文件为缺省的multiserver配置文件: multiserver.conf, 这个文件位于enhydra的根目录下
start 脚本不是一个配置文件, 他指定应用程序将要用到的路径(CLASSPATH)
特别注意: 如果你不在这个文件中指定CLASSPATH , 应用程序将会使用系统的CLASSPATH, 那样可能不正确
如果你编辑配置文件, 你可以改变文件中不同的设置,下面将会具体解释
注意 应用程序向导在<app_root>/input(例如 simpleApp/input)目录下创建"input"版的配置文件. 你应当编辑这些文件. 运行make 时会在<app_root>/output(例如simpleApp/output)目录下创建"runtime" 版的配置文件,你不用编辑output目录中的这些配置文件,因为你每次编译整个项目时,这个文件就会被input目录中相应的文件覆盖
Multiserver 配置文件包含了Multiserver 用来运行应用程序的信息, 如下

应用程序配置文件包含下面重要信息

打开管理控制台(Admin Console)


你可以通过管理控制台管理enhydra应用程序, 例如Java servlets, 还有Web archives (WAR 文件).在这个基于web的控制台中, 你可以增加,删除,启动,停止应用程序,修改应用程序设置,完成一些基本的调试.
在这一节中, 我们将在shell下登陆管理控制台, 增加simpleApp应用程序, 用控制台来启动和停止simpleApp应用程序. 并且修改和调试应用程序 ,就像Developer's Guide(注:这本书不是免费的)中第三章"Using the Multiserver Administration Console," 谈到的
打开控制台:


在shell窗口下输入:
multiserver

在浏览器中输入下面这个URL:
http://localhost:8001/


管理控制台显示一个登陆对话框. 输入缺省用户名: admin, 和密码: enhydra, 登陆后显示如图4.3
图形4.3 显示管理控制台

 

在控制台中加入simpleapp


 

下面这几步将把simpleApp 加入到Multiserver


指定连接方法


启动和停止应用程序
--------------------------------------------------------------------------------

新添加的的应用程序或servlet处于停止状态,为使其运行,

使用XMLC


XMLC, 即xml编译器, 已经在第3章, "Overview."中介绍过,它是你创建应用程序强有力的工具,通过它

你可以将用户界面和后台事务逻辑完全分离开来
注意: 通常XMLC 可以处理XML 文件, 但是由于实际的原因,本章剩余部分将集中讲其怎样处理HTML 文件.
XMLC 分析一个HTML文件并且创建一个Java 对象,这个对象可以在运行时处理HTML 文件中的内容, xmlc不考虑html文件的格式编排. XMLC创建的java对象有根据DOM(Document Object Model)标准来的接口定义,DOM标准来自W3C(World Wide Web Consortium).
增加一个计数器
--------------------------------------------------------------------------------


为了更深入了解XMLC 怎样工作, 接下来你要为你的应用程序增加一个计数器,这个计数器显示了有多少人曾经访问过你的应用程序.


1:在presentation目录中找到Welcome.html 和WelcomePresentation.java 文件

2:在Welcome.html 文件中</CENTER> 标记前加入下面一行代码:
<P>Number of hits on this page: <SPAN ID="HitCount">no count</SPAN>


ID 属性告诉XMLC 产生一个与<SPAN>标记相对应的对象, 这样可以在运行时取代文本"no count"
3:在WelcomePresentation.java中加入下面蓝色的代码.
// Add the following line.
static int hitCount=0; // All Welcome PO's will share this.

public void run(HttpPresentationComms comms)
throws HttpPresentationException, IOException {

HttpPresentationOutputStream out;
WelcomeHTML welcome;
String now;
byte[] buffer;

welcome = (WelcomeHTML)comms.xmlcFactory.create(WelcomeHTML.class);
now = DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());
welcome.getElementTime().getFirstChild().setNodeValue(now);
// Increment the count and write into the html.
// Add the following line.
welcome.setTextHitCount( String.valueOf( ++hitCount ) );
buffer = welcome.toDocument().getBytes();
comms.response.setContentType( "text/html" );
comms.response.setContentLength( buffer.length );
out = comms.response.getOutputStream();
out.write(buffer);
out.flush();
}

}

4:

在simpleApp程序的根目录下输入make编译整个应用程序. 然后重新启动Enhydra, 或者通过管理控制台(见"打开管理控制台(Admin Console)") 或者在shell 窗口下输入下列指令.
cd /myapps/simpleApp
make
cd output
./start


用 make编译应用程序时, XMLC 将会处理所有的HTML 文件, 在这里仅处理 Welcome.html一个文件.


5:在浏览器中输入http://localhost:9000 测试这个程序.
浏览器将会显示simpleApp的欢迎页面(WelcomePresentation.po). 你会看到欢迎页面在redirect连接下有了一个计数器如图4.6.

图形4.6 带计数器的simpleApp 的欢迎页面


这个页面现在可以显示有多少人访问过.


6:为验证计数器是否工作,你可以重新加载一下这个页面(即在浏览器中刷新即可).
你会发现你每次访问这个程序一次,计数器就会加一.

应用程序作了下面两件事:


回想一下Presentation Manager 为每个请求实例化一个表现层对象. 所以,浏览器每发出一次请求 WelcomePresentation 类就被实例化. 由于hitCount 是静态变量, 她被所有的WelcomePresentation对象共享,所以每次请求它的值都会加一.
就像为<SPAN ID="time">标记创建getElementTime() 方法一样, XMLC 为<SPAN ID="HitCount">标记创建setTextHitCount() 方法. 应用程序使用setTextHitCount() 方法将hitCount 写入到网页中.
注意: XMLC创建了WelcomeHTML 类, 单缺省情况下它删除了Java 源文件.
理解文档对象模型(Document Object Model)
--------------------------------------------------------------------------------


HTML 文档有一个层次化或树状的结构,这种结构可以被Java这样的面向对象语言模型化. W3C (Worldwide Web Consortium) 关于XML/HTML 对象模型的标准叫做Document Object Model (DOM).
Enhydra 应用程序在运行时用DOM 操作HTML 内容. 例如假定有下面的HTML代码:
<TABLE>
<TR>
<TD ID="cellOne">Shady Grove</TD>
<TD ID="cellTwo">Aeolian</TD>
</TR>
<TR>
<TD ID="cellThree">Over the River, Charlie</TD>
<TD ID="cellFour">Dorian</TD>
</TR>
</TABLE>


这个HTML代码片断有一个<TABLE> 标记,其中它包含了 <TR>标记, <TR>中又有<TD> 标记,<TD>则有文本 (或者其他数据). 这就定义了一个树状的层次结构, 如图4.7.

Figure 4.7 HTML的DOM 树



每个方框和椭圆都是这个树中的一个节点(node). 在这种层次结构中一个节点上面的节点叫做父节点(parent).父节点下面的节点叫做孩子节点(children).
每个节点可以有自己的属性,就像html标记一样(例如, 一个表格就可以有背景颜色这个属性). W3C 定义了一些包和接口来反映HTML文档中的节点对象的层次. 而且, XMLC 包含了改变属性值的API
例如用下面的代码来改变一个表格的背景颜色:
HTMLTableCellElement cellOne = theDocument.getElementCellOne();
cellOne.setBgColor("red")
;


在这个例子中,HTMLTableElement 类和setBgColor()方法来自于 W3C 的包; getElementCellOne() 方法来自XMLC.

这段代码说明 这样一件重要事情,XMLC确实创建方法来访问DOM中的节点
. XMLC 产生GetElementxxx() 方法并且返回一个对象,这个对象同带ID 属性的标记交互.你可以直接单独用W3C的类来改变一个表格的颜色, 但你的代码不得不遍历整个DOM 树, 那将是非常困难的.
SPAN 和DIV 标记
<SPAN> 和<DIV> 或许是html标记中你不熟悉的.通常在使用样式表(CSS)时才会用到他们. 除此之外他们被浏览器忽略. 但是, XMLC 扩展了他们的用途.


在命令行上使用XMLC
--------------------------------------------------------------------------------


以前,用make编译项目时是隐含的运行XMLC. 为了能在命令行上运行 XMLC , 在你CLASSPATH中必须有enhydra.jar 文件. make 运行时他会设定 CLASSPATH . 在命令行上使用XMLC之前你必须将enhydra.jar 放到你的CLASSPATH 中去.
用下列命令设定CLASSPATH :
UNIX: export CLASSPATH=<enhydra_root>/lib/enhydra.jar:
Windows: export CLASSPATH=<enhydra_root>/lib/enhydra.jar\;


要想得到更多的关于配置Enhydra的信息,请参考cd上的安装配置指南
XMLC 的基本命令行语法为
xmlc -options file.html


options 为一系列的命令行选项, file 是输入文件的名字.
有很多命令行选项. 在这里我们介绍三个非常有用的: -dump, -class, 和-keep.
-dump 选项
-dump 选项使XMLC 显示文档的DOM树. 这主要用于学习; 一旦你熟悉了XMLC, 你会还少用它.


在simpleApp/src/simpleapp/presentation目录中创建一个文件Simple.html

文件内容如下:
<HTML>
<HEAD>
<TITLE>Simple Enhydra Page</TITLE>
</HEAD>
<BODY>
<H1 ID="MyHeading">Ollie Says</H1>
The current time is <SPAN ID="time">00:00:00</SPAN>.
</BODY>
</HTML>

进入presentation 目录输入下列命令:
xmlc -dump Simple.html


XMLC 将会在shell窗口中显示如下:

 

LazyHTMLDocument:
   HTMLHtmlElementImpl: HTML
       HTMLHeadElementImpl: HEAD
         HTMLTitleElementImpl: TITLE
            LazyText: Simple Enhydra Page
      HTMLBodyElementImpl: BODY
         HTMLHeadingElementImpl: H1: id="MyHeading"
            LazyText: Ollie Says
         LazyText: The current time is
         LazyHTMLElement: SPAN: id="time"
            LazyText: 00:00:00
         LazyText: .


每一行显示一个DOM对象名然后是一个冒号,紧接着是与之对应的 HTML 标记. 如果标记有属性, 属性将会在标记之后列出. 例如, HTMLHeadingElement 是<H1>标记的DOM名, 它有一个属性名为"MyHeading."
缩进式的层次表示对象之间的关系. 例如你可以看到 HTMLHeadingElement 是HTMLBodyElement的孩子.

-class 和-keep 选项

缺省情况下XMLC创建于html文件名相同的类名,例如对于Simple.html,它将创建Simple.java
如果要创建一个不同名字的类名,可以使用-class 选项来指定类名.
缺省情况下, XMLC 删除他创建的java源文件, 仅保留class 文件.源文件对于你理解XMLC 和DOM API 怎样工作十分有用. 使用-keep 选项保留Java源文件.


1.例如为Simple.html创建一个java对象,名为SimpleHTML 并且保留Java 源文件,输入下列命令:
xmlc -keep -class simpleapp.presentation.SimpleHTML Simple.html


XMLC 产生两个文件: SimpleHTML.java 和SimpleHTML.class.


2.打开SimpleHTML.java 看一下产生的代码.
在这个文件内, 你会发现两个方法, getElementMyHeading() 和getElementTime(). XMLC识别出Simple.html文件内的heading和<SPAN>标记的 ID 属性并产生了这两个方法. 看一下整个文件你会更好的理解xmlc为这个简单文档创建的对象.


3.一旦你看完了 SimpleHTML.java 和SimpleHTML.class,删除他们.

现在你已经探究了xmlc怎样工作,不要删除Simple.html因为后面我们要用到它


Enhydra 编程
--------------------------------------------------------------------------------

这一节将讲述更多Enhydra 程序开发的实质性的主题


1.保持session 状态

2.为应用程序增加新的页面

3.填充表格

4.加入business 对象
Maintaining session state
--------------------------------------------------------------------------------


因为HTTP 是一个没有国界的协议, 一个需要对不同请求保存特定用户信息的应用程序必须实现 session 维护. 关于Enhydra 怎样完成维护session , 请参考"Session Manager."
想象session是这样一个容器:应用程序可以把任何于特定用户相关的信息放入其中. 你使用的作为容器的类为 com.lutris.appserver.server.session.SessionData. 它与哈希表非常相似,它有下列方法


set():传递一个 string key 和object 并且存储

get():返回object, 给出string key

Enhydra 为每个用户匹配一个带session key的 SessionData 对象, .session key是一个非常长的随机的字符串,当Enhydra Session管理器最初为用户创建SessionData 对象时, 它产生一个session key 并且存储在其内部数据结构中. Enhydra 同时将session key 传给客户端,或者作为cookie传递,或者附加在URL上. 下一次客户端发出请求时, Session 用session key 找到用户的SessionData 对象.
通常你不必担心session key--Enhydra 为你操作这一切,你需要做的仅仅是通过get 和set跟踪好你放在session里的对象.
为了使你更好的理解session 维护, 你将会将强你的应用程序以便使欢迎页面显示某个特定用户访问过其几次, 同时显示访问总量.而且可以在页面上显示session key .


1;在Welcome.html</CENTER> 标记前加入下面代码
<P>Number of hits from you:
<SPAN ID="PersonalHitCount">no count</SPAN>
<P>Session identifier:
<SPAN ID="SessionID">no count</SPAN>

2:

在WelcomePresentation.java中加入:
import com.lutris.util.*;

3:在WelcomePresentation.java, 加入下面的成员变量(放在hitCount变量之后即可):
final String hits = "HITS";


"HITS" 是应用程序用来保存和调用计数器值得关键字.


4:在run() 方法中加入下面的代码, 将其放在setTextHitCount 方法之后即可.
你也可以在<enhydra_root>/doc/books/getting-started/samples/ 目录中的SessionMaint.java中找到这些代码
try {
Integer personalHits =
(Integer)comms.session.getSessionData().get(hits);
if(personalHits == null) {
personalHits = new Integer(1);
} else {
personalHits = new Integer(personalHits.intValue() + 1);
}
comms.session.getSessionData().set(hits, personalHits);
// Save personalHits to the user's session.
welcome.setTextPersonalHitCount( personalHits.toString() );
welcome.setTextSessionID( comms.session.getSessionKey() );
// Shows the session key value used for session tracking.
} catch (KeywordValueException e) {
comms.response.writeHTML("Session access error" + e.getMessage());
}


这段代码通过调用getSessionData().get(hits)得到作为关键字存储在"HITS."字符串中的值,由于 SessionData 仅能存储一般的java.lang.Objects 对象, 你不得不将其转换为整形(Integer). 如果对象没有存储在session中, 这段代码将会产生一个新的整形值1. 如果对象存在,其值将会增1.
通过setSessionData().set(hits, personalHits)将其值写入session,然后用getSessionKey()将session里的数据写入到网页中. 正常情况下,你不需要处理session key,但由于好奇心的缘故,这个例子向你展示了怎样显示它.


5:重新编译整个程序并启动它,然后在浏览器中访问它
欢迎页面将会显示访问总数, 同样也会显示某个用户的访问次数, 同时还有唯一的Session 标示符如图4.8所示.

Figure 4.8 显示带Session 标示符的欢迎界面

由于你在你的本地机子上运行此应用程序,不可能有其他的客户访问,所以这些数字是一样的

但是如果这个程序放到互联网上,你会发现你访问页面的次数与访问总数是不同的. 注意session ID 一直不会变.

为应用程序增加新的页面
--------------------------------------------------------------------------------


接下来我们将为应用程序增加新的页面(HTML文件和表现层对象) . 你将使用前面创建的一个小的HTML 文件:Simple.html. 除了学会怎样增加一个页面, 你将研究DOM 并且对它更熟悉
增加一个表现层对象:

将文件WelcomePresentation.java 复制为SimplePresentation.java.

打开SimplePresentation.java 将类WelcomePresentation 改为SimplePresentation.

删除所有session相关的代码.

将所有的WelcomeHTML 替换为SimpleHTML.

将welcome 标示符改为simple.
现在你已经有了一个表现层对象.改后的run() 方法应该如下所示:
public void run(HttpPresentationComms comms)
throws HttpPresentationException, IOException {

HttpPresentationOutputStream out;
SimpleHTML simple;
String now;
byte[] buffer;

simple = (SimpleHTML)comms.xmlcFactory.create(SimpleHTML.class);
now = DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date());
simple.getElementTime().getFirstChild().setNodeValue(now);
buffer = simple.toDocument().getBytes();
comms.response.setContentType( "text/html" );
comms.response.setContentLength( buffer.length );
out = comms.response.getOutputStream();
out.write(buffer);
out.flush();
}

在文件的顶部加入下列import信息:
import org.w3c.dom.*;
import org.w3c.dom.html.*;

在run()方法中的最后一个语句之前加入下列代码:
HTMLHeadingElement heading = simple.getElementMyHeading();
heading.setAttribute( "align", "center" );
Text heading_text = (Text) heading.getFirstChild();
heading_text.setData( "Mr. Ollie Otter says:" );


这段代码作下面几件事:


1.从DOM 得到名为MyHeading的HTMLHeadingElement 对象

2.设置ALIGN 属性为CENTER, 使标题居中

3.得到标题的孩子对象(即一个文本对象)

4.设置新的文本字符串为:"Mr. Ollie Otter says:"

你可以通过在标题周围的文本放置<SPAN>标记来做同样的事. XMLC将会产生一个方法setTextMethod() 你可以在代码中使用它. 但这个例子描述了怎样通过DOM来处理它.
注意:这段代码完成了一些底层的 DOM 操作,你在你的程序中可能用不到,因为这些代码违反了表现层于事物逻辑层分离的原则. 这里仅仅用来帮助解释DOM.


编辑presentation目录中的Makefile文件将新文件加入到CLASSES 和HTML_CLASSES变量中去,如下所示:
CLASSES = \
RedirectPresentation \
WelcomePresentation \
SimplePresentation
HTML_CLASSES = \
WelcomeHTML \
SimpleHTML


当你在presentation运行make时, HTML_CLASSES变量别传递给XMLC -class 选项来创建指定的类. 这正是你在命令行下用xmlc加-class选项所作的,. Lutris 的规定为 为foo.html文件创建fooHTML 类,这个规定在这个文件中有定义:<enhydra_root>/lib/stdrules.mk


保存所有的文件,在presentation目录下运行make 编译整个包.
缺省情况下在应用程序的根目录下的classes中产生包,在这个例子中是simpleApp .看一下classes/simpleapp/presentation 目录中产生的文件.

为你的新页面在欢迎页面中创建一个连接:

在Welcome.html中加入下面的html代码:
<A HREF="SimplePresentation.po">Go to Simple Page</A>

如果你还没有那样做, 先在shell窗口下用ctrl+c中止应用程序, 然后在应用程序根目录编译:
cd /enhydra/myapps/simpleApp
make
cd output
./start

像以前一样,在浏览器中访问应用程序.

点击 Go to Simple Page 连接看一下SimplePresentation PO.
Simple 表现层对象只有一个标题和当前时间,在下一节中你将会使这个页面变得更吸引人.

图形4.9 浏览Simple presentation object


填充表格
--------------------------------------------------------------------------------

在web开发中另一个常见的任务就是用动态的数据填充表格. 这一节将讨论用一个静态的字符串数组作为数据源来填充表格. 接下来的一章, 你将会修改代码,使用数据库作为数据源.
用下列步骤来填充一个表格:


1.在HTML文件中创建表格

2.编程填充表格

3.重新编译并运行应用程序

在html中创建表格
在Simple.html 文件中创建一个带id属性的模版行


编辑presentation/Simple.html .

在</BODY> 标记前加入下列代码.
注意:如果你不想手工键入这些代码, 你可以从<enhydra_root>/doc/books/getting-started/samples/TableCode.html这个文件中复制粘贴过来.
<H2 align=center>Disc List</H2>
<TABLE border=3>
<TR>
<TH>Artist</TH> <TH>Title</TH> <TH>Genre</TH>
<TH>I Like This Disc</TH>
</TR>
<TR id=TemplateRow>
<TD><SPAN id=Artist>Van Halen</SPAN></TD>
<TD><SPAN id=Title>Fair Warning</SPAN></TD>
<TD><SPAN id=Genre>Good Stuff</SPAN></TD>
<TD><SPAN id=LikeThisDisc>Yes</SPAN></TD>
</TR>
</TABLE>


这段HTML代码包含了一个模版行(第二个<TR> 标记).注意这一行和填充行单元内容的<SPAN>标记都有ID属性. 之所以叫模版行, 因为它只是一个模型,你需要用它来为表格构建更多的行.
编程填充表格
为了编程实现填充表格,需要编辑与Simple.html对应的表现层对象. 接下来的几步中, 你会为SimplePresentation.java 增加代码,从字符串数组中反复的取出元素来填充表格.


复制<enhydra_root>/doc/books/getting-started/samples/TableCode.java. 到你的应用程序的presentation 目录重命名为 SimplePresentation.java.
注意: 只要你喜欢的话,你可以将老的SimplePresentation.java 改名为SimplePresentation.sav 作为将来的参考.


现在看一下新的SimplePresentation.java.

除了表现层对象的基本特征外,首先你需要注意的是成员变量是一个字符串数组,应用程序将用这个数组内的元素来填充表格,在这个例子中,数组取代了数据库中的纪录作为数据源:
:
String[][] discList =
{ { "Felonious Monk Fish", "Deep Sea Blues", "Jazz", "Yes" },
{ "Funky Urchin", "Lovely Spines", "Techno Pop", "Yes" },
{ "Stinky Pups", "Shark Attack", "Hardcore", "No" } };


接下来的代码片断得到表格中的元素的文档对象:
HTMLTableRowElement templateRow = simple.getElementTemplateRow();
HTMLElement artistCellTemplate = simple.getElementArtist();
HTMLElement titleCellTemplate = simple.getElementTitle();
HTMLElement genreCellTemplate = simple.getElementGenre();
HTMLElement likeThisDisc = simple.getElementLikeThisDisc();


下面的代码为每个对象删除id属性. 原因是DOM 在文档中每个ID 是唯一的. 当你复制了表格中的一行时, 你的文档中会有重复的ID.
removeAttribute() 删除指定名字的属性:
templateRow.removeAttribute("id");
artistCellTemplate.removeAttribute("id");
titleCellTemplate.removeAttribute("id");
genreCellTemplate.removeAttribute("id");
likeThisDisc.removeAttribute("id");


然后调用getParentNode() 得到表格对象, 如下所示:
Node discTable = templateRow.getParentNode();


紧接着的是关键代码, 一个for循环不断的从数据集(这里只是个数组)中取出每一行,将文本填充到表格的行中, 并且将每一行追加(或者说是克隆)到表格中:
for (int numDiscs = 0; numDiscs < discList.length; numDiscs++) {
simple.setTextArtist(discList[numDiscs][0]);
simple.setTextTitle(discList[numDiscs][1]);
simple.setTextGenre(discList[numDiscs][2]);
simple.setTextLikeThisDisc(discList[numDiscs][3]);
discTable.appendChild(templateRow.cloneNode(true));
}


最后一句是至关重要的, cloneNode() 方法创建了 Node 对象的一个拷贝并调用它; 在这里,节点对象为templateRow. 布尔变量true 表示只复制节点本身还是包括其孩子节点以及孩子节点的孩子节点. 在这个例子中为true是因为我们不仅复制这行还有其孩子节点(表格单元还有其中的文本).
最后, removeChild() 方法从表格中删除模版行. 这就保证了运行时不会有重复的数据.
discTable.removeChild(templateRow);


重新编译并运行
在浏览器中访问这个页面.


如果你还没有那样做, 可以在shell窗口下输入ctrl+c中止multiserver, 在应用程序根目录下编译:
cd /enhydra/myapps/simpleApp
make
cd output
./start

现在可以像以前一样在浏览器中访问应用程序.

点击Simple Page 连接看一下新的SimplePresentation PO.
Simple 表现层对象现在有了一个光盘列表的表格如图4.10所示.

图形4.10 填充表格后的simple页面



增加一个business 对象
--------------------------------------------------------------------------------


到目前为止,你的应用程序已经有三个对象: SimpleApp 应用程序对象, 两个表现层对象: Welcome和Simple. 接下来的一节你将为你的应用程序添加一个business 对象. 这不会改变应用程序的显示.
business 对象表示一个光盘的列表,这或许不会十分有用,但它确实描述了business 对象所扮演的角色
增加business 对象:

在simpleApp/src/simpleapp/business目录中创建一个新文件SimpleDiscList.java ,因为SimpleDiscList.java 属于你的应用程序的business 包, 所以其第一行代码为:
package simpleapp.business;

增加下面几行(可以从SimplePresentation类中复制粘贴过来,但是注意discList前要有下划线 )
public class SimpleDiscList {
String[][] _discList =
{ { "Felonious Monk Fish", "Deep Sea Blues", "Jazz", "Yes" },
{ "Funky Urchin", "Lovely Spines", "Techno Pop", "Yes" },
{ "Stinky Pups", "Shark Attack", "Hardcore", "No" } };
public SimpleDiscList() {
}
public String[][] getDiscList() {
return _discList;
}

}

为保证这个文件被正确的编译,需要编辑business目录中的make 文件:Makefile, 在CLASSES 变量中加入类名,如下:
CLASSES = \
SimpleDiscList


这个make 文件自动被顶层的make文件包含, 你需要为你的项目增加的就是这么简单的代码. 确信在business 目录下输入make.


现在返回到presentation 目录, 编辑SimplePresentation.java 如下:


Import the new class:
import simpleapp.business.SimpleDiscList;


增加下面两行创建一个business 对象的实例, 调用getDiscList() 方法.这两行将取代前面一节将的静态数组的初始化.
SimpleDiscList sdl = new SimpleDiscList();
String[][] discList = sdl.getDiscList();


重新编译整个应用程序.

你不会看到你的程序有什么变化, 但是你已经将表现层对象中的功能放到了business 对象中. 接下来的一章我们将用真正的数据库查询取代静态数组,这将会更方便, . 那样的话你不用改变表现层对象,因为business 对象在表现层和数据层之间提供了一个缓冲.


为应用程序建立数据库连接
--------------------------------------------------------------------------------


Enhydra使用Java Database Connectivity (JDBC): 一个标准的Java API, 来与数据库通信. Enhydra 可以连接任意与JDBC兼容的数据库, 例如Oracle, Sybase, Informix, Microsoft SQL Server, PostgreSQL, 和InstantDB.
在你为应用程序进行数据库连接之前, 有一些基础工作要做. 特别是下面的几件工作:


1:为应用程序创建数据库和表

2:对数据库建立JDBC 连接进行测试

3:配置 Enhydra的数据库管理器(Database Manager) 通过JDBC进行数据库连接

特别注意: 在这一节中, 假定你已经安装了InstantDB 并且设置了CLASSPATH 环境变量包含 InstantDB 的JAR 文件. 请参考Lutris Enhydra 光盘上的安装指南. 如果你使用其它的数据库, 你也需要编辑CLASSPATH .
创建数据库表
--------------------------------------------------------------------------------

这一节剩余部分需要你的数据库中存在指定的表,所以你需要在进行连接之前建立表.

这里将讲述怎样在 InstantDB 中建立表.
注意: 大多数数据库提供了直接执行SQL 语句的工具. 例如, InstantDB 提供了ScriptTool , Oracle 支持SQL*Plus. 但是数据库与数据库之间的sql格式是不一样的,这一点要特别注意. 本节提供的 SQL 例子只适用于InstantDB, 对于其他数据库可能无效.
在InstantDB中创建一个表:


为数据库创建一个新的目录:data.
cd /enhydra/myapps
mkdir data

从InstantDB的Examples目录下复制简单的属性文件: sample.prp到 data 目录, 重命名为simpleApp.prp. 在命令行上输入如下:
cd /data
cp /idb/Examples/sample.prp simpleApp.prp

复制<enhydra_root>/doc/books/getting-started/samples目录下的tutorial_create_idb.sql到data目录,命令行输入如下:
cp /usr/local/lutris-enhydra3.5/doc/books/getting-started/samples/tutorial_create_idb.sql


这个SQL文件包含了包含了创建一个简单的表:LE_TUTORIAL_DISCS的sql语句, 还有许多INSERT 语句将数据填充到表中去. 接下来的一节我们将用到这个表.


在命令行上输入下列命令运行ScriptTool ,用tutorial_create_idb.sql文件作为创建LE_TUTORIAL_DISCS 表的输入文件.
java org.enhydra.instantdb.ScriptTool tutorial_create_idb.sql


看一下data目录,你会注意到:新的目录已经被创建,包括tables 目录.
注意:samples 包含了用于Oracle的SQL 文件.,下面就是在命令行上用SQL*Plus 创建表的命令:
SQL> @<enhydra_root>/doc/books/getting-started/samples/tutorial_create.sql

使用InstantDB的DBBrowser 来检验表是否已经创建.


输入下列命令启动DBBrowser:
java -Xms16m -Xmx32m org.enhydra.instantdb.DBBrowser


在DBBrowser中, 单击Browse 选择数据库. 选定simpleApp.prp 后点击Open.

点击Connect 连接到数据库.

在Tables栏中选择LE_TUTORIAL_DISCS , 点击Submit 来查询数据库.
缺省的查询语句为SELECT * FROM LE_TUTORIAL_DISCS, DBBrowser 将会显示下面的数据.
Rockin Apps,Enhydra Orchestra,Rock and Roll,1
Beethoven Symphony No.9,LA Philharmonic,Classical,1
Material Girl,Madonna,Modern Rock,0


点击Disconnect 断开与simpleApp 数据库的连接,然后关闭DBBrowser.

注意: samples 目录包含了一个SQL 文件,:tutorial_create.sql, 这个文件用于Oracle . Here is the 在Oracle 的SQL*Plus上输入如下:
SQL> @<enhydra_root>/doc/books/getting-started/samples/tutorial_create.sql


如果你用的数据库不是InstantDB, 请参考你的数据库文档中关于执行SQL文件或者创建表格的部分

 

建立JDBC 连接
--------------------------------------------------------------------------------


在你创建数据库应用程序之前,你需要在你的系统和数据库服务器之间建立JDBC连接, 数据库服务器可能运行在另一个操作系统上. 这一节将告诉你怎样编写和执行一个建立JDBC连接的独立的应用程序. 用一个独立的应用程序会让你同可能发生的问题隔离开来. 如果你已经在你的系统上配置了JDBC,你可以跳过这一节..
在这个简单的程序中,我们将做下面这些事情:


为使程序能够运行, 你必须安装配置好了你的数据库, 并且创建了LE_TUTORIAL_DISCS 表.
注意: 这个例子只能与InstantDB一起工作. 如果你想使其适用于其他的数据库, 你需要改变程序中的关于驱动程序的信息, 比如连接字符串. 参见附录A,:"Database configurations,"来得到更多的其他数据库的配置信息
在windows系统下 Cygnus 工具要求你的JDBC 驱动程序安装在C 盘, 所以一定要保证JDBC 驱动程序库在c盘上.
为了使用这个程序,你需要做下面工作:


1:创建一个jdbcTest 目录并使其成为当前目录. 例如在你的数据库的tmp 目录下创建,输入如下命令:
cd /enhydra/myapps/data/tmp
mkdir jdbcTest
cd jdbcTest

2.从samples目录中拷贝JDBCTest.java 到你新建的目录,例如:
cp usr/local/lutris-enhydra3.5/doc/books/getting-started/samples/JDBCTest.java


看一下程序代码核实一下JDBC 驱动程序和连接字符串. JDBC 驱动程序和连接字符串在下面的代码中用粗体表示.

import java.sql.*;

public class JDBCTest {
   public static void main( String[] args )   {
      Connection con  = null;
      Statement  stmt = null;
      ResultSet  rs   = null;
// Load the driver, get a connection, create statement, run query, and print.
      try {
         // To test with a different database, replace JDBC driver information below
         Class.forName("org.enhydra.instantdb.jdbc.idbDriver");
         /* To test with a different database, provide appropriate connection data
                           (e.g., database connection string, username, and password) */
         con = DriverManager.getConnection(
      "jdbc:idb:/enhydra/myapps/data/simpleApp.prp");
         stmt = con.createStatement();
         rs = stmt.executeQuery("SELECT * FROM LE_TUTORIAL_DISCS");
         rs.next();
         System.out.println("Title = " + rs.getString("title") + 
            " -- Artist = " + rs.getString("artist"));
                        con.close();
      }
      catch(ClassNotFoundException e) {
         System.err.println("Couldn't load the driver:    "+ e.getMessage());
      }
      catch(SQLException e) {
         System.err.println("SQLException caught: " + e.getMessage());
      }
   }
}

 

 

3.编辑JDBCTest.java, 如果需要的话,可以改变连接字符串. 连接字符串出现在try块第二个语句中的
getConnection()中(见前面的代码).


4.用javac命令编译这个文件:
javac JDBCTest.java

5.运行程序:
java JDBCTest


如果你已经在表中添加了数据(前面已讲过),你会在shell 窗口下看到如下内容:
main SELECT * FROM LE_TUTORIAL_DISCS
Title = Rockin Apps -- Artist = Enhydra Orchestra
Database simpleApp is shutting down...
Database simpleApp shutdown complete.


如果发生了错误,你也会在 shell窗口下看到许多异常信息,这些信息将帮助你解决问题.注意参考一下你的数据库的JDBC 文档, 设置好数据库驱动程序和连接字符串. Enhydra的邮件列表示一个很好的资源. 参见"Enhydra.org mailing lists" 来得到更多的关于邮件列表的信息.
配置应用程序以使用JDBC
为了使JDBC 可以在Enhydra 应用程序中使用, 你必须将JDBC 驱动程序在系统的CLASSPATH变量中设定好.在你应用程序的的start脚本中设定CLASSPATH 也不失为一个好主意.这样就可以避免你有多个程序使用不同的驱动程序引起的混乱. 这也使你的应用程序具有更好的移植性.
共有两个start脚本: 模版脚本start.in 位于应用程序的input 目录, start脚本位于output 目录. 当你编译应用程序时input目录中的模版脚本将会覆盖output目录中的脚本. 因此你需要在input目录中的模版脚本中设置CLASSPATH .
注意: Enhydra 有其自己的类装载器, 所以如果你通过应用程序的配置文件中指定jdbc驱动程序来将jdbc驱动程序放入到 CLASSPATH中,驱动程序将不会工作.
将JDBC 驱动程序放置到你的应用程序的CLASSPATH中:


1:编辑simpleApp/input/start.in 在"Build up classpath:"部分的开始处加入下面几行
CLASSPATH="<JDBC_LIB>"
export CLASSPATH


<JDBC_LIB> 指jdbc驱动程序库 (通常为一个.jar 或.zip 文件), 包括路径. 例如:
CLASSPATH=/idb/Classes/idb.jar\;/idb/Classes/jta-spec1_0_1.jar


注意这一行不要有空格,否则脚本将不会正确执行.

注意: 为了更易维护和移植, 最好将 JDBC驱动程序库中的路径设为一个变量. 如果你改变驱动程序并且需要更新 CLASSPATH时这将会节约你很多时间. DiscRack 和AirSent 项目就是这么做的,这两个项目位于<enhydra_root>/examples 目录下

.

配置数据库管理器
--------------------------------------------------------------------------------

现在你已经验证了你的jdbc连接,你需要为simpleApp程序提供连接参数,可以在应用程序中的配置文件:simpleApp.conf中设定这些参数,在这里,你需要编辑其模版文件simpleApp.conf.in,当所有文件编译完后,make工具将会把它拷贝到output/conf 目录中
.


在文本编辑器中打开simpleApp/input/conf/simpleApp.conf.in .

在文件的末尾加入下面这些内容:

#-----------------------------------------------------------------------------
#                   Database Manager Configuration
#-----------------------------------------------------------------------------
DatabaseManager.Databases[] = "simpleApp"
DatabaseManager.DefaultDatabase = "simpleApp"
DatabaseManager.Debug = "false"
DatabaseManager.DB.simpleApp.ClassType = "Standard"
DatabaseManager.DB.simpleApp.JdbcDriver = "org.enhydra.instantdb.jdbc.idbDriver"
DatabaseManager.DB.simpleApp.Connection.Url = 
"jdbc:idb:/enhydra/myapps/data/simpleApp.prp"
DatabaseManager.DB.simpleApp.Connection.User = ""
DatabaseManager.DB.simpleApp.Connection.Password = ""
DatabaseManager.DB.simpleApp.Connection.MaxPoolSize = 30
DatabaseManager.DB.simpleApp.Connection.AllocationTimeout = 10000
DatabaseManager.DB.simpleApp.Connection.Logging = false
DatabaseManager.DB.simpleApp.ObjectId.CacheSize = 20
DatabaseManager.DB.simpleApp.ObjectId.MinValue = 99


注意: 这仅仅是适用于InstantDB的一个配置文件.对于其他数据库, 参见附录A, "Database configurations."

检查所有的斜体条目,使其与你的应用程序匹配

jdbc:idb:/enhydra/myapps/data/simpleApp.prp

注意:保证文件的末尾是一个回车符,这是文件能正确工作的要求.


保存并关闭配置文件.

编译应用程序,例如:
cd /enhydra/myapps/simpleApp
make

增加数据库访问功能
--------------------------------------------------------------------------------


现在你已经有了一定的基础,你将准备为simpleApp增加数据库访问能力. 这一节将讲述怎样通过嵌入的sql取代"填充表格"中用到的静态数组.下一节, "使用DODS," 将讲述怎样用dods建立一个真正的"real" 数据层.
首先,你将创建一个data 对象来查询数据库.


复制<enhydra_root>/doc/books/getting-started/samples目录中的SimpleDiscQuery.java到你的data 层(即simpleApp/src/simpleapp/data 目录).

当重新编译项目时为保证SimpleDiscQuery.java 得到编译, 编辑data 目录中的make文件为CLASSES变量增加类名:
CLASSES = \
SimpleDiscQuery


斜杠后面(\)不能有空格, 只能是回车符.


看一下SimpleDiscQuery.java. 注意导入语句放到顶部:
import java.sql.*;


这个语句告诉你这个类会用到JDBC. 除了构造方法外, 仅有一个方法: query(), 在这个方法中完成大多数实际的工作. 构造函数仅有一句:
connection = Enhydra.getDatabaseManager().allocateConnection();


这一句告诉 Enhydra 数据库管理器分配一个数据库连接. 然后, query() 方法调用executeQuery 执行sql查询语句:
resultSet = connection.executeQuery("SELECT * FROM LE_TUTORIAL_DISCS");


query()剩余的代码遍历SELECT语句查询的结果, 通过Vector, vResultSet(其中包含Vector), vRow(每一行的数据)返回 . 尽管每一行包含四个数据 (因为表中有四列), 但有多少行却是未知的, 这就是这个方法返回Vector的原因.
这里我们将重新使用前面的Simple 表现层对象,但是其中的二维字符串数组将不会用到. 所以需要对SimpleDiscList 进行一些改变. 编辑你前面创建的business 对象:SimpleDiscList.java:


找到<enhydra_root>/doc/books/getting-started/samples/SimpleDiscList.java.
你可以用它替换你的旧的SimpleDiscList.java , 或者你愿意的话,你可以手工编辑旧的文件:


在顶部加入下面两个导入语句:
import simpleapp.data.SimpleDiscQuery;
import java.util.*;


为data对象加入一个成员变量:
SimpleDiscQuery _sdq;


用新文件中的代码取代getDiscList() 方法的主体.它将query()返回的矢量数组中的矢量转化为二维的字符串数组,表现层的对象将会用到这个数组.

注意你不用改变表现层的对象. data 对象在表现层对象和数据对象之间提供了一个缓冲.


现在编译整个项目,输入命令如下:.
cd /enhydra/myapps/simpleApp
make

.
The Disc List table in your Simple presentation object should now contain data from your database, as shown in Figure 4.11.

编译完后运行应用程序,现在光盘列表表格中已经包含了数据库中的数据,如图4.11所示

图形4.11 Simple PO (使用数据库的光盘列表)


你已经创建了第一个数据库查询页面,注意页面中显示的光盘数据来自数据库,而不是静态数组

使用DODS
--------------------------------------------------------------------------------


DODS (Data Object Design Studio)是一个图形化的对象和关系型数据的映射工具 ,它能产生创建数据库表的 SQL 代码,并能产生相应的访问数据库表的应用程序代码.dods为不同的数据库生成相应的处理代码 , 所以你不必知道数据库之间的细微差别. DODS 同时也处理常见的问题:如事物处理和数据的完整性. DODS 在你开始建立数据库或者修改数据库结构时是非常有用的,参见"Loading the schema."一节
在前面的一节中,我们创建了一个表: LE_TUTORIAL_DISCS来存储音乐精选光盘的信息. 在这一节,我们将在这个主题的基础上,使用DODS 创建数据库规划和相应的data 对象,在data对象中将"owner" 和每一个"disc."关联起来.在第五章, "DiscRack 应用程序."中我们将对这个主题做更深入的开发.
注意: 在你开发Enhydra数据库应用程序时,可以不用dods,但它能大大的简化你的工作.
运行DODS
--------------------------------------------------------------------------------

这一节我们将介绍怎样使用 DODS,并且预览一下下一节我们将要创建的数据库
.


1.在命令行上输入下面的命令启动 DODS
dods


你将会看到DODS 的图形界面.


2.点击File|Open,选择<enhydra_root>/examples/DiscRack/discRack.doml 打开DiscRack项目的dods文件
如图 4.12,你可以展开左下方面板中的目录,看一下data包的层次结构.

图形4.12 打开了DiscRack.doml的DODS

DODS 窗口中有三个子面板:


1.在顶部, 对象面板显示了各个实体之间关系的模型.每个数据对象(与数据库中的表相对应) 用一个带有名字的方框表示. 方框之间的红线表示了数据对象之间的关系.

2.在左下方, 包(package)面板显示了当前数据模型的层次关系,目录表示对象模型, 蓝点表示包中的对象.

3.在右下方,属性面板显示了所选中的数据对象的属性(即属性和列). 网格中的每一行代表一个属性, 每个属性中的信息是不同的, 例如数据类型,是否可以为空 .

DiscRack 数据库规划如图4.12. DODS 展示了数据库规划和对象模型的各种特征,例如disc 数据对象有

title artist 字段, 他们是java 类的属性 (或者成员) , 就像列和数据库表之间的关系一样.
DODS 创建了操作对象的java 代码和数据库操作的SQL 代码(例如person 和discs之间的一对多关系).

图形4.13 DiscRack 对象模型/规划



创建数据层
--------------------------------------------------------------------------------


现在你将要开始为的应用程序创建其数据层. DODS 提供了设计数据模型的一种方法,通过Enhydra应用程序框架产生相应的数据层的类. DODS 同时也产生在数据库中创建表的SQL 代码.
在这一节中数据模型将被用作 DiscRack应用程序的数据层 (见第五章, "DiscRack 应用程序") 但要对包做一个小小的更名.
用DODS创建数据层需要做下面几项工作:


1.定义包的层次结构

2.定义数据对象

3:产生data层代码

定义包的层次结构

定义包的层次结构即是建立dods产生的源代码的文件层次结构.


1.选择File|New,创建一个新的数据模型.
DODS 将关闭所有打开的数据模型文件建立一个新的,新的数据模型中只包含一个名为"root" 的包.


2:选择Database 菜单,选则你的数据库类型
例如如果你用Oracle, 选择Database|Oracle.这会保证dods产生的sql语句对你的数据库来说合法.在本例中使用InstantDB,选择Standard JDBC.


3:现在创建包的层次关系:


改变root包的名字.

在左下方的面板中选中root目录, 选择菜单Edit|Package.在弹出的对话框中输入此应用程序的名字: 点击Enter.


增加data包:选中simpleApp 目录,选择菜单Insert|Package. 输入data, 点击Enter.


类似的创建discperson 目录作为data 目录的子目录.
注意Java中的包名要以小写字母开头. 包(package)面板如下图所示:

图形4.14 包的层次结构


定义数据对象
DODS 数据模型中的数据对象与数据库中的表相对应,数据对象中的属性描述了数据库表中的列.


选择左下方面版中的person包, 选择菜单Insert|Data Object.
你将会看到数据对象编辑对话框:

图形4.15 DODS 数据对象编辑对话框


改变缺省的数据对象如下:


在Class 选项中, 在Name 栏中输入Person.

在Package 选项中,选中person 包.

在DataBase选项中, 在db Table Name中输入Person.

注意类名以大写字母开头(Person), 包名以小写字母开头 (person).


保留剩余的参数不变点击OK.
现在你已经在你的数据模型中有了一个数据对象: Person .接下来, 当你用build命令产生data层时, DODS 将会产生在数据库中创建Person表的sql语句. DODS 也会产生一个Person 对象,你可以在你的应用程序中使用.


选择左下方面版中的disc包, 点击菜单Insert|Data Object.

在数据对象编辑器的对话框中, 改变缺省的数据对象如下:


在Class 选项中, 在Name 栏中输入 Disc .

在Package 选项中, 选中disc包.

在DataBase选项中, 在db Table Name中输入 Disc .


保留剩余的参数不变点击OK.
现在你已经在你的数据模型中有了一个数据对象: Disc


为Person增加login属性:


选中Person 数据对象(在simpleApp 对象模型中用蓝点表示), 点击菜单Insert|Attribute 打开属性编辑对话框, 如图4.16.

图形4.16 DODS 属性编辑对话框


在General 选项中, 在Name栏中输入login.

在Java 选项中, 在Java Type 下拉菜单列表中选择String来设置数据类型. String 是缺省的Java 数据类型
注意: 当你在下拉菜单中选择了java数据类型后,Database选项中db Type栏就会自动设定相应的数据库类型. 例如,你在java选项中设定java type为String , 相应的database中的db Type栏会自动设定为 VARCHAR.


在Database 选项中, 保留db Type 为VARCHAR 并且选择 Can Be Queried.

保留剩余的参数不变点击OK.

注意:login 属性出现在dods窗口右下方的面板中 (见图形4.12). 这个面板左边的几栏显示的是你在属性编辑器中设定的选项., 例如黄色的Q 符号意味着数据对象can be queried. 你可以将鼠标移到其他符号上,看看他们代表什么意思.


重复上述过程增加下面三个属性( Java Type 为String ,db Type为VARCHAR)到Person数据对象中:


password

firstname

lastname

技巧: 你可以通过工具栏上的增加按钮打开属性编辑对话框.


向前面一样为Disc数据对象( Java Type 类行为String,db Type为VARCHAR) , 增加下面三个属性


title

artist

genre


为Disc数据对象定义 owner 属性.
增加owner 比增加前面讲的那些属性要麻烦一些, 因为一个Person可以拥有多个Disc.


选中Disc 数据对象,点击菜单Insert|Attribute 打开属性编辑对话框.

在General 选项中, 在Name栏中输入 owner

在Java 选项中, 在Java Type下拉列表中选择simpleapp.data.person.PersonDO.

在Database 选项中, 选择Be Queried 和Referenced DO Must Exist选项.

点击OK 关闭属性编辑对话框.

你会注意到顶层面板中 Disc 对象和Person对象之间有红箭头,他表示Disc 对象用到了Person对象. Notice that the attribute pane displays icons indicating that the owner attribute has an object reference and a referential constraint.


为Disc数据对象定义isLiked 对象.


选中Disc 数据对象,单击菜单Insert|Attribute 打开属性编辑对话框.

在General 选项中, 在Name栏中输入 isLiked .

在Java 选项中, 在Java Type下拉列表中选择 boolean .

在Database 选项中, 选择 Can Be Queried.

点击OK 关闭属性编辑对话框.

你已经完成了Person 和Disc数据对象的定义.
产生data层代码
为完成这个练习, 保存并建立数据模型.

1.选择File|Save 保存数据模型文件.
将数据模型文件:simpleApp.doml 保存在应用程序的根目录下 (例如/enhydra/myapps/simpleApp).
注意: DODS 将数据模型文件存为 .doml . DOML 表示Data Object Meta Langauge. DOML有与xml相似的语法.
现在你已经定义好了所有的数据对象, DODS 已经准备产生和编译代码了. Build All 命令将会让DODS 用新的代码覆盖data 目录中已有的的代码, 所以如果你想保留旧的data 层代码, 请将它复制到其他目录.


2.选择菜单File|Build All 打开Destroy and Create New DODS Directory 对话框.


如果建立data层是成功的,你会看到这条消息:DODS BUILD COMPLETE. 如果失败,看一下输出屏幕中的出错信息,检查文件的路径是否正确,你也可以搜索 .doml 文件来得到更多线索,这种文件非常容易阅读
DODS 在data目录中产生下列文件和子目录:


每个数据对象目录包含了四个java源文件,例如person数据对象目录包含 personDO, personDOI, personQuery, 和personDataStruct四个类.数据对象和查询类是最常用的类.
DODS 同时也为data 层产生make文件. 这使你可以单独编译或者与整个项目一起编译. classes这个空目录只有你单独编译data层时才会用到.
下一步运行dods产生的sql脚本,在数据库中建立表. 这个工作在下一节做.
Loading the schema
--------------------------------------------------------------------------------


图形4.19 描述了DODS产生的完整的:

图形4.19 DiscRack database schema generated by DODS

注意与原始的数据库规划不一样:


DISC 和PERSON 表中增加了两个字段: OID 和VERSION

并且增加了一个表: OBJECTID:这个表只有一个字段 :NEXT

OID 字段时DODS产生的每个表的主关键字. DODS产生的应用程序代码确保了数据库中的每一行中的OID的值是唯一的.无论何时向表中增加一行,应用程序将会为 OID列增加一个唯一的.object ID . OBJECTID 表是用来跟踪下一个将要分配的 object ID .
DODS应用程序代码用每个表中的 VERSION列来保证应用程序正在更新的数据是正确的. 因为可能有多个用户同时在操作数据库,在应用程序得到记录和试图改变记录的时间间隔内,记录就可能被改变.
每次应用程序更新一行,数据库中的VERSION列将会增加. 应用程序保证更新 VERSION 和OID 列--如果它发现没有一行有他期望的值,它就会知道它试图改变的行已经被其它进程修改了,这时应用程序就会抛出异常,你可以捕获这个异常并做适当的处理.
运行dods产生的脚本
请按照下列步骤加载DODS产生的sql脚本:


打开位于<simpleApp_root>/simpleApp/data 目录的create_tables.sql
这个文件包含创建PERSON, DISC,和OBJECTID表的语句:
/* This SQL was generated for a Standard database. */
create table Person
(
/* class Person */
login VARCHAR(32) DEFAULT "" NOT NULL ,
password VARCHAR(32) DEFAULT "" NOT NULL ,
firstname VARCHAR(32) DEFAULT "" NOT NULL ,
lastname VARCHAR(32) DEFAULT "" NOT NULL ,
oid DECIMAL(19,0) NOT NULL PRIMARY KEY,
version INTEGER NOT NULL
);

/* This SQL was generated for a Standard database. */
create table Disc
(
/* class Disc */
title VARCHAR(32) DEFAULT "" NOT NULL ,
artist VARCHAR(32) DEFAULT "" NOT NULL ,
genre VARCHAR(32) DEFAULT "" NOT NULL ,
owner DECIMAL(19,0) NOT NULL REFERENCES Person ( oid ) ,
isLiked INTEGER DEFAULT 0 NOT NULL ,
oid DECIMAL(19,0) NOT NULL PRIMARY KEY,
version INTEGER NOT NULL
);

create table objectid(
next DECIMAL(19,0) NOT NULL
);


注意: DODS产生的sql文件可能与你的数据库不兼容,你可以手工删除可能引起错误的无关的文本,例如对于 Oracle, 你需要删除所有的空白行.


如果必要的话, 你可以编辑一个在你的数据库上能够工作的create_tables.sql.
samples 目录(<enhydra_root>/doc/books/getting-started/samples/) 有一个create_tables.sql 文件,这个文件已经被编辑可以工作于InstantDB,要想与InstantDB一起工作,需要做下面的改变 :


1.在文件顶部加入下面几行,这几行加载 JDBC驱动程序并打开数据库:
d org.enhydra.instantdb.jdbc.idbDriver;
o jdbc:idb=<database_path>;

2.在<database_path> 上输入数据库的属性文件.例如 /enhydra/myapps/data/simpleApp.prp.


3.在每一个SQL 语句的前面加个e . 例如, create table Disc 变为e create table Disc.

4.将双引号("") 改为('').

5.在文件末尾加入下面一行来关闭数据库
c close;

你可以通过修改配置文件:<enhydra_root>/dods/dods.conf来改变DODS产生的sql文件中的许多东西.

例如缺省情况下, DODS 产生类似c语言的注释,如果你的数据库要求用其它方式的注释,你可以在这个文件中修改.


在数据库中建立表.
对于InstantDB, 在命令行上用 ScriptTool加载sql文件:
java org.enhydra.instantdb.ScriptTool create_tables.sql


对于Oracle, 在SQL*Plus 的命令行上输入:
SQL> @<simpleApp_root>/simpleApp/data/create_tables.sql

为了测试在数据库中增加一些虚构的数据
For InstantDB, 用ScriptTool 和tutorial_insert_idb.sql 文件(位于<enhydra_root>/doc/books/getting-started/samples) 为数据库增加数据:
java org.enhydra.instantdb.ScriptTool tutorial_insert_idb.sql


对于Oracle, 用SQL*Plus 和tutorial_insert.sql增加数据:
SQL> @/<enhydra_root>/doc/books/getting-started/samples/tutorial_insert.sql


注意:tutorial_insert_idb.sql和tutorial_insert.sql 包含了许多样本数据, 包括一个person 和三个discs.
使用DODS 数据对象
--------------------------------------------------------------------------------

现在,为了用DODS产生的data层对象取代以前创建的非常简单的对象:SimpleDiscQuery.java,你需要修改 business对象:SimpleDiscList
.

用<enhydra_root>/doc/books/getting-started/samples/目录中的SimpleDiscList_DODS.java取代你的应用程序中business目录中的的SimpleDiscList.java,并改名为SimpleDiscList.java
.

看一下新的SimpleDiscList 对象中的代码和旧的 SimpleDiscList.java 做一下比较.
新旧两个对象的主要区别在getDiscList() 方法. 下面是这个方法的核心代码:
...
try {
DiscDO[] discArray;
DiscQuery dquery = new DiscQuery();
discArray = dquery.getDOArray();
String result[][] = new String[4][discArray.length];
for(int i=0; i< discArray.length; i++) {
result[i][0] = (String)discArray[i].getTitle();
result[i][1] = (String)discArray[i].getArtist();
result[i][2] = (String)discArray[i].getGenre();
result[i][3] = discArray[i].getIsLiked() ? "Yes" : "No";
}
}
return result;
...


新的代码使用 data.disc包中的DiscQuery 和DiscDO 对象从数据库中取得数据.


DiscQuery 提供了一系列的查询 DISC表的方法. 缺省情况下, 它执行这个语句:SELECT * FROM DISC. 这个类也提供进行条件查询(例如SELECT语句中的WHERE 条件)和对结果排序的方法 . getDOArray() 方法从查询结果中返回一个DiscDO 对象的数组.

DiscDO 对象是一个基本的数据对象,它代表DISC 表中的一行. 对表中的每一列它都有 get 和set 方法. 前面的代码只用到get方法:getTitle(), getArtist(), getGenre(), 和getIsLiked(). getIsLiked() 放回一个 boolean 值, 其它的方法返回string.为保持一致,我们将根据getIsLiked()方法返回的值做一下逻辑变换,将boolean值改为适当的string.

运行应用程序
现在我们将重新编译并运行simpleApp 应用程序.


在应用程序的根目录输入make 进行编译,例如:
cd /enhydra/myapps/simpleApp
make

启动Enhydra: 可以通过启动管理控制台(见前面"打开管理控制台") 或者在shell窗口下输入命令window.
cd output
./start

在浏览器中输入http://localhost:9000 测试应用程序.
现在Simple表现层对象中的光盘列表中的数据来自你数据库中的 Disc 表.

图形4.20,从dods数据对象中取得数据的Simple PO


如果你没有看到这个页面,作如下检查:


1.看一下output目录中的simpleApp.conf 文件,确信数据库的设置是正确的

2.当你启动Multiserver时看一下shell窗口的错误输出. 如果simpleApp.conf中的数据库设置正确I且 CLASSPATH中包含用到的JDBC 驱动程序,Multiserver启动时应该没有错误.

3.重新运行一下 JDBC 连接测试程序,检查数据库是否正确和JDBC 是否工作.

4:对于Oracle 数据库,试一下在应用程序的配置文件中使用一个错误的密码. Multiserver 也会启动,但应用程序将会返回一个SQL 异常和stack trace.

5:确信没有其它的 JVM(java虚拟机)在运行. 有时候, 类装载器不会找到正确的类,因为它可能使用一个正在运行的JVM的旧的 CLASSPATH