Part I: 在AppFuse建立DAO和对象 - 一个建立对象(代表数据库的表)和把这些对象存储到数据库的Java类的教程。
本教程将向你展示如何在一个数据库里创建表,以及如何完成访问这些表的Java代码。
我们将建立一个对象以及处理(保存/检索/删除)这些类到数据库的一些代码。用Java术语,我们叫它Plain Old Java Object(a.k.a. a POJO
一个数据访问对象Data Access Object (a.k.a. a DAO
), 一个 Interface
和一个Hibernate实现
一个 JUnit
类来测试我们的DAO对象
NOTE: 如果你使用MySQL并且希望使用事务 (很有可能是这个情况),你需要使用InnoDB tables,为了做到这一点, 添加以下两句话到 (/etc/my.cnf 或者 c:/Windows/my.ini)。 其中第二个设置 (设置使用UTF-8字符)是 4.1.7 +所必需的。
[mysqld]
default-table-type=innodb
default-character-set=utf8
如果你使用PostgreSQL并且在成批处理时得到许多迷惑的错误,试着把关闭它,方法是增加 <prop key="hibernate.jdbc.batch_size">0</prop> 到你的 src/dao/**/hibernate/applicationContext-hibernate.xml文件。
AppFuse使用Hibernate 作为持久化层, Hibernate是一套对象/关系Object/Relational (O/R)框架,他允许你把Java对象和数据库之间联系起来,它可以很方便的对你的对象执行CRUD (Create, Retrieve, Update, Delete)操作。
你也可以选择使用iBATIS 作为持久化层,如果要在AppFuse里安装iBATIS, 请查看extras/ibatis中的README.txt。如果你选择iBATIS而不是Hibernate, 希望你有自己的原因并且熟悉这个框架,我也希望你能够领会到如何将教程应用到iBATIS ;-)
字体惯例 (进行中)
要在命令行下执行的命令是这个样子: ant test-all.
对目录或者包中的文件的引用是这个样子: build.xml.
我在“真实世界”中实际操作的方式用蓝色斜体表示。
让我们继续在AppFuse项目的结构下创建一个新的对象、DAO和测试。
[2] 使用Ant根据对象建立数据库中的表
[3] 创建一个DAOTest来运行DAO对象的JUnit测试
[4] 创建一个新的DAO来执行关于这个对象的CRUD操作
[5] 在spring里配置Person和PersonDAO
[6] 运行DAOTest
建立一个对象,并且作XDoclet标记 [#1]
我们要做的第一件事情就是建立一个需要持久化的对象,我们要在src/dao/**/model目录下建立一个简单的Person对象,这个对象包括id、firstName和lastName属性。
注意: 直接拷贝本教程的代码 在FireFox下无效 ,但我们可以通过CTRL+Click选定一个代码所在的工作区(OS X下是Command+Click),然后再拷贝。
|
这个类必须扩展BaseObject ,而这个BaseObject有三个抽象方法(equals(), hashCode()和toString())需要你在Person类里实现,前两个是Hibernate的需要。为了完成这部分工作最简单的方式是使用Commonclipse ,关于这个工具更多的信息可以在Lee Grey的网站 里看到,另外一个你可以使用的Eclipse的插件是Commons4E ,我还没有使用过,这里不便对其功能作出评论。
如果你使用IntelliJ IDEA ,你可以自动产生equals()和hashCode(),但没有toString(),有一个 ToStringPlugin 插件做得非常不错
现在我们已经创建了这个POJO对象,我们需要增加XDoclet标记来产生Hibernate的映射文件,这些文件用来映射对象→ 表和属性(变量) → 字段。
首先,我们增加@hibernate.class 来告诉Hibernate我们将要和那个表作关联:
|
我们也要增加主键的映射,否则XDoclet会在产生映射文件时出错,注意所有的@hibernate.*标签必须在getters'的Javadocs里面。
|
我使用generator-class="increment"而不使用generate-class="native" 是因为我对数据库使用"native"时发现了一些问题,如果你只是希望使用MySQL,推荐使用"native"值,本教程使用increment。
使用Ant根据对象产生数据库表[#2]
在这种情况下,你可以通过运行ant setup-db来建立person表,这个任务会产生文件Person.hbm.xml并且会建立叫做"person"的表,从Ant的控制台窗口,你可以看到Hibernate为你建立的表结构的内容。
[schemaexport] create table person (
[schemaexport] id bigint not null,
[schemaexport] primary key (id)
[schemaexport] );
如果你查看Hibernate生成的文件Person.hbm.xml,可以到build/dao/gen/**/model目录,这里是Person.hbm.xml的内容(目前的内容):
|
现在我们要为其它的字段(first_name, last_name)添加额外的@hibernate.property 标签:
|
在这个例子里,添加column属性的唯一原因是因为这个字段名与它的属性名不相同,如果他们相同,你没有必要来指定column属性,关于其它可以使用的标签请看@hibernate.property 。
再次运行ant setup-db把新加的属性加到数据库表里。
[schemaexport] create table person (
[schemaexport] id bigint not null,
[schemaexport] first_name varchar(50),
[schemaexport] last_name varchar(50),
[schemaexport] primary key (id)
[schemaexport] );
如果期望修改字段的长度,修改@hibernate.property标签的length属性,如果希望把字段改为必添字段(NOT NULL),可以增加属性not-null="true"。
建立新的DAOTest来对你的DAO运行JUnit测试[#3]
注意:从Appfuse版本 1.6.1 +开始包括了一个AppGen工具,可以用来生成本教程余下的所有的类的代码,不过,我们最好还是先过一遍教程再使用这个工具产生代码。
现在,我们要创建一个DAOTest来测试我们的DAO的工作,“等会儿”,你说,“我们还不曾创建DAO呢!”,你说得对。无论如何,我发现测试驱动开发 大大的促进了软件质量,在许多年里我一直认为在写代码之前写测试是胡说八道,这看起来很愚蠢,但当我尝试之后我认为这样非常好,现在我按照测试驱动的方式工作完全因为我发现这样可以大大提高我软件开发的效率。
开始,我们在test/dao/**/dao目录下建立类PersonDAOTest.java,这个类必须扩展BaseDAOTestCase ,而BaseDAOTestCase这个类是JUnit类TestCase 的子类,这个类用来加载Spring 的ApplicationContext(因为Spring把各个层绑定)和单元测试类同一目录下同你的测试类文件同名的.properties文件(ResourceBundle),这个属性文件的属性可以通过“rb”属性来访问。
我经常拷贝(打开→另存为)一个已存在的测试(如UserDAOTest.java),然后查找/替换 [Uu]ser为[Pp]erson,或者任何其它需要替换的内容。
|
以上是我们使用JUnit测试而初始化和销毁PersonDAO的基本代码,对象“ctx”引用了Spring的ApplicationContext,它在BaseDAOTestCase 类的静态代码区里被初始化。
现在我们需要实际测试DAO中的CRUD(create, retrieve, update, delete)方法,为此我们需要为每个方法建立以test(全部小写)开头的测试方法,只要这个方法是公共的,返回类型是void,它们就会被我们build.xml中的Ant的<junit>任务调用,如下是一些简单的CRUD测试,需要注意的一点是所有的方法(或者叫做测试)必须是自治的,添加如下代码到文件PersonDAOTest.java:
|
在testGetPerson方法,我们创建了一个person并且调用get方法,我通常会增加一条我所需要的记录到数据库,因为在测试运行之前DBUnit 会为数据库准备测试数据,我们可以简单的在metadata/sql/sample-data.xml里添加测试所必须的记录
<table name='person'>
<column>id</column>
<column>first_name</column>
<column>last_name</column>
<row>
<value>1</value>
<value>Matt</value>
<value>Raible</value>
</row>
</table>
通过这种方式你可以在testGetPerson方法里消除创建新纪录的动作,如果你愿意直接插入记录到数据库(使用SQL或者GUI),你可以用ant db-export和cp db-export.xml metadata/sql/sample-data.xml重新构建你的sample-data.xml文件。
在上面的例子里,你可以看到我们调用person.set*(value)来准备我们需要保存的对象,在这个例子里很简单,但是当你要插入10条必添字段(not-null="true")时就比较麻烦了,这就是我为什么要在BaseDAOTestCase使用ResourceBundle文件,只要在PersonDAOTest.java同一个目录创建一个PersonDAOTest.properties并且在里面定义你的属性值:
我通常只是在Java里硬编码,但是这个.properties对于大对象很有用。
firstName=Matt
lastName=Raible
此时,你要通过调用BaseDAOTestCase.populate(java.lang.Object)方法来准备对象,而不是使用person.set*。
|
在目前情况下,还不可以编译PersonDAOTest,因为在类路径里还没有PersonDAO.class,我们需要创建它。PersonDAO.java是一个接口,PersonDAOHibernate.java是它的Hibernate实现,让我们继续,开始创建。
创建一个对对象执行CRUD操作的新DAO[#4]
马上,在src/dao/**/dao目录里建立PersonDAO.java接口,并且指定所有实现类要实现的基本CRUD操作,为了显示方便,我已经去掉了所有JavaDocs。
|
注意,在以上的方法声明上并没有exceptions说明,这是因为Spring 使用RuntimeExceptions来包裹Exceptions的方式,此时,你已经可以使用ant compile-dao来编译src/dao和test/dao下的所有源文件,然而当你运行ant test-dao -Dtestcase=PersonDAO进行测试时,你会得到一个错误:No bean named 'personDAO' is defined,这是一个Spring的错误,说明你必须在applicationContext-hibernate.xml指定一个名字为personDAO的bean,在此之前我们需要创建PersonDAO的实现类。
运行dao测试的ant任务叫做test-dao,如果你传递testcase参数(用-Dtestcase=name),它会查看**/*${testcase}*允许我们传递Person、PersonDAO、或者PersonDAOTest以及所有会执行PersonDAOTest的类。
让我们创建一个实现PersonDAO的类PersonDAOHibernate并使用Hibernate来get/save/delete这个Person对象,为此,我们在src/dao/**/dao/hibernate创建一个新类PersonDAOHibernate.java,它应该扩展BaseDAOHibernate ,并且实现PersonDAO。为了简洁,省略Javadocs。
|
现在,如果你运行ant test-dao -Dtestcase=PersonDAO,你会得到同样的错误,我们必须配置Spring来让它知道PersonDAOHibernate是PersonDAO的实现,同样的,我们也要告诉它还有个Person对象。
配置Spring中的Person和PersonDAO [#5]
首先我们要告诉Spring所有Hibernate文件的位置,为此,打开src/dao/**/dao/hibernate/applicationContext-hibernate.xml,在以下代码块添加"Person.hbm.xml"。
|
现在我们需要添加一些XML数据来绑定PersonDAOHibernate到PersonDAO,为此,添加如下代码到文件底部:
|
你也可以为<bean>使用autowire="byName"属性来消除"sessionFactory"属性。
从个人来讲,我喜欢在XML文件里保留对象的依赖。
运行DAOTest[#6]
保存所有修改的文件,运行ant test-dao -Dtestcase=PersonDAO。
Yeah Baby, Yeah:
BUILD SUCCESSFUL
Total time: 9 seconds
Part II: 创建管理器Manager - 创建与数据库端(DAOs)交互的业务Facades和事务处理。
本教程依赖于Part I:在AppFuse建立DAO和对象.
本教程将会向你展示如何创建一个业务Facade类(和一个JUnit Test)与Part I中创建的DAO交互操作。
在AppFuse的语境下,这被称作一个Manager类,它的主要职责是持久户层(DAO)和web层之间的一个桥梁,它也很好的把展示层和数据库层(例如Swing应用)解耦,Managers必定是应用程序所有的业务逻辑所在的地方。
我在“真实世界”中实际操作的方式用蓝色斜体表示。
让我们从在AppFuse的框架下创建一个ManagerTest和Manager。
[1] 创建一个新的运行JUnit测试的ManagerTest
[2] 创建一个新的与DAO通讯的Manager
[3] 为这个Manager和事务配置Spring
[4] 运行ManagerTest
在Part I,我们创建了一个Person对象和一个PersonDAO对象 - 所以我们继续开发这个实体,首先,我们创建PersonManager的JUnit test,在test/service/**/service目录下创建PersonManagerTest,我们会希望在DAO对象同样的基本方法(get, save, remove) 测试。
这看起来是多余的(为什么全是测试!),但如果是一个6个月的过程这个测试是非常重要的。
这个类必须扩展service包下的BaseManagerTestCase ,这个类(BaseManagerTestCase)的功能与BaseDAOTestCase类似。
我通常会修改(打开 → 另存为)存在的测试(如UserManagerTest.java),查找/替换[Uu]ser with [Pp]erson,或者其他任何我的对象的名字。
以下代码是一个基本的Manager的JUnit测试的要求,与DAOTest不同,这个测试使用jMock 来吧Manager和他的依赖隔离,使它成为一个真的"单元" 测试。这可以使你只关心业务逻辑而不必担心它的依赖,以下代码简单的设置好Manager和它 的依赖。
|
现在你已经把类的骨架搭好了,你需要添加肉了:填写确保所有测试通过的代码,以下来自DAO Tutorial的片断帮助我们理解我们将要做的事情。
...我们创建以"test"(全部小写)开头的方法,只要这些方法是public,返回类型是void,并且没有参数,它们就会被<junit>调用,以下是为了测试简单的CRUD操作,一件需要记住的事情是每一个方法(也可以称作测试)必须是自制的。
添加如下方法到PersonManagerTest.java:
|
这个类不会被编译,因为我们还没有创建PersonManager接口。
在AppFuse里遵从这么多规范来实现可扩展性看起来很可笑,事实上,在绝大多数我参与的项目里 - 我发现在一年里学了如此多的知识,以至于我不想扩展我的架构,我想去重写它,我希望通过采纳最佳实践来保持AppFuse的时效性,但这并不经常发生,每年都仅仅是一个道最新版本的升级,而不是一个重写,;-)
创建一个新的与DAO通讯的Manager[#2]
马上,为所有实现类在src/service/**/service目录创建一个PersonManager.java接口来指定基本的CRUD操作,为了显示的目的,我去掉了所有的JavaDocs, setPersonDAO()方法不是在所有的情况下出现,只是因为PersonManagerTest可以把DAO赋值。
通常,我会复制(打开 → 另存为)一个已存在的文件 (例如UserManager.java).
|
我们创建一个PersonManagerImpl类来实现PersonManager中的方法,为此,在src/service/**/service/impl创建一个PersonManagerImpl.java类,他必须扩展BaseManage并且实现PersonManager。
|
需要注意的是setPersonDAO()方法,Spring使用它来绑定PersonDAO到Manager,这些配置在applicationContext-service.xml 文件,我们将在Step 3[3]配置这些,现在你可以使用"ant compile-service"编译所有代码。
现在你需要为服务层配置Spring文件,它才会知道这个新的Manager。
为这个Manager和事务配置Spring[#3]
为了通知Spring我们的PersonManager接口和它的实现类,打开src/service/**/service/applicationContext-service.xml,你会看到注释掉的关于"personManager"的定义,去掉注释,或者直接在文件末尾添加:
|
"parent"属性会引用一个TransactionProxyFactoryBean bean的定义,这也是所有的事物对象所要设置的。
运行ManagerTest[#4]
保存所有的文件,并且运行ant test-service -Dtestcase=PersonManager。
Yeah Baby, Yeah:
BUILD SUCCESSFUL
Total time: 9 seconds
此刻所有修改后的文件可以从这里下载 .
下一部分: Part III: 创建Actions和JSPs - 在AppFuse架构下创建Actions和JSPs。
第三部分 创建Action和JSP - 讲述如何在你的AppFuse工程里创建 WebWork Action和JSP。
阅读本部分指南请先阅读 第二部分 创建新的Manager对象.
这个部分将会展现给你如何创建Webwork框架的Action和JSP,同样会编写JUnit测试来测试PersonAction对象。
我会用这种格式说明我在 实际过程 中的操作。
接下来我们开始在AppFuse的构架下创建一个新的Acetion和JSP。如果还没有安装WebWork模块,先运行ant install-webwork任务。
[1] 使用XDoclet创建JSP
[2] 创建PersonActionTest以便测试
[3] 创建PersonAction对象
[4] 运行PersonActionTest
[5] 清理JSP文件并进行发布
[6] 创建Canoo WebTests模拟测试浏览器行为进行单元测试
使用XDoclet创建JSP [#1]
在这一步,我们要创建JSP文件显示Person对象的信息。 这个JSP将使用Webwork的JSP Tag为Person.java中的每个属性在表单的表格产生一行。这个AppGen工具是基于StrutsGen工具实现的 - 我们使用的这个工具是由Erik Hatcher 开发的。他由一个类(FormTagsHandler.java) 和一对XDoclet模版(FormKeys.xdt and Form_jsp.xdt组成)。这些文件都在extras/viewgen目录下。
产生一个表单元素和对应的标签属性文件只需经过下面几个基本步骤:
命令行下面切换到"extras/viewgen"目录下
执行ant -Dform.name=Person任务 在extras/viewgen/build 目录下产生下面三个文件:
Person.properties (表单元素的标签)
PersonForm.jsp (显示一个Person信息的JSP文件)
PersonList.jsp (显示People列表的JSP文件)
把Person.properties文件中的内容拷贝到web/WEB-INF/classes/ApplicationResources_en.properties文件中,这是所有form中需要的标题和属性键值。下面是一个具体的Person.properties文件例子:
# -- person form --
person.id=Id
person.firstName=First Name
person.lastName=Last Name
person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.
# -- person list page --
personList.title=Person List
personList.heading=Persons
# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
把PersonForm.jsp文件拷贝到web/pages/personForm.jsp目录下。把PersonList.jsp拷贝到web/pages/personList.jsp目录下。 注意每个文件名的名字第一个字符是小写字符。
"pages"目录下的文件在发布时将发布到"WEB-INF/pages"目录下。容器提供了WEB_INF目录下的所有文件的安全控制。这意味着来自客户端的请求,而不是由发过来的请求将不被相应。把所有的JSP文件放到WEB-INF目录下可以保证用户只能通过Actions来访问这些页面。这样就把系统安全性要求全部转移到Actions上,那里可以得到更高效的处理并且在基本的表示层外面。
AppFuse架构的web应用程序安全性保证了所有*.html文件得到了保护(除了/signup.html和/passwordHint.html文件)。这就保证了客户端必须通过Action才能访问JSP文件(至少所有在pages目录下的JSP是这样)。
注意: 如果自定义了CSS特殊页面,那么需要在文件的最上面加上。这样就可以被 SiteMesh 识别并且放到最终的页面中去。你也可以使用下面的语句一个一个页面的使用自定义CSS:
body#pageName element.class { background-color: blue }
在ApplicationResources_en.properties文件中加入对应JSP文件的titles和headings键值。
在自动产生的JSP文件中,有两个键表示title (浏览器的标题)和header (页面的标题)。我们需要在ApplicationResources_en.properties文件中说明这两个键的值(personDetail.title和personDetail.heading),具体内容如下所示:
# -- person detail page --
personDetail.title=Person Detail
personDetail.heading=Person Information
在这上面我们要在文件中加入"personForm.*"键,为什么我们要使用personForm和personDetail这样的表示形式?最主要的原因是能够明显区别页面的输入框标签和普通文本信息。另外一个原因是因为*Form.*这种形式可以为数据库中的提供很好的表现形式。
最近我有个客户希望所有数据库中的字段都可以查询。这个相当容易实现。我在ApplicationResources.properties 检查所有的包含"Form."的键并且把它放到下拉列表框中。在用户界面上,用户可以输入查询项并且选择想要查询的列。I was glad I followed this Form vs. Detail distinction on that project!
为PersonAction创建一个JUnit测试类, 首先在test/web/**/action目录下创建PersonActionTest.java文件。
|
由于没有创建PersonAction类这个对象无法通过编译。
创建PersonAction类 [#3]
在src/web/**/action目录下,创建PersonAction.java文件:
|
有一些键值(Key)需要加到ApplicationResources_en.properties文件中用来显示给用户看的操作成功的提示信息打开web/WEB-INF/classes目录下的ApplicationResources_en.properties并且加入如下内容:
我通常把这些内容加在# -- success messages --注释下面。
person.added=Person has been added successfully.
person.updated=Person has been updated successfully.
person.deleted=Person has been deleted successfully.
当然你可以加入一般的added, deleted和updated提示信息,这取决于你的需要。为每个实体使用独立的信息可以在特别情况下改变它。
你可能注意到了我们以和PersonManager类类似的方式调用了TestPersonManager对象。PersonAction和PersonManagerTest都是PersonManagerImpl的客户,这是个很好的结构。
现在你需要告诉Spring和WebWork这个action的存在。首先在web/WEB-INF/action-servlet.xml文件中加入如下的PersonAction定义信息:
|
然后在web/WEB-INF/classes/xwork.xml中加入引用信息说明:
|
在上面的说明中"validationStack" 拦截器引用被注释掉了,因为你没有为Person对象定义任何的validation规则.。我们会在下一章中加入校验规则并且去掉注释。
运行PersonActionTest [#4]
如果你看了我们的PersonActionTest, 所有的测试依赖一条id为1的纪录(testRemove依赖一套id为2的纪录),所以让我们在示例数据文件metadata/sql/sample-data.xml中加入这些纪录(原来已经添加了一条,现添加第二条)。如下所示(顺序并不重要)
<table name='person'>
<column>id</column>
<column>first_name</column>
<column>last_name</column>
<row>
<value>1</value>
<value>Matt</value>
<value>Raible</value>
</row>
<row>
<value>2</value>
<value>James</value>
<value>Davidson</value>
</row>
</table>
当我们运行任何测试时,DBUnit都会加载这个文件,因此当你运行Action test时这些纪录肯定是有效的。
确定你在project目录下并且所有文件都正确保存,运行ant test-web -Dtestcase=PersonAction任务,结果正如我们期望的那样。
BUILD SUCCESSFUL
Total time: 21 seconds
清理JSP以便正常显示Person信息 [#5]
现在清理自动产生的personForm.jsp文件隐藏"id"属性。把下面的代码从 web/pages/personForm.jsp文件中移除掉:
|
在<table>标签后加入下面的代码:
|
如果希望提高界面的易用性,你可以把焦点设置在第一个字段的文本框上。这需要加入下面的JavaScript代码:
<script type="text/javascript">
document.forms["person"].elements["firstName"].focus();
</script>
现在执行 ant db-load deploy, 任务, 启动Tomcat并且在浏览器中指向http://localhost:8080/appfuse/editPerson.html?id=1 , 你可以看到下面的界面:
最后,为了增加页面的用户友好性,你可以在表单的最上面加入标题信息,这个很容易实现只需要在personForm.jsp页面的最上方加入"<fmt:message>"就可以了。
[可选部分] 创建Canoo WebTest模拟浏览器行为进行单元测试 [#6]
最后一步可(可选)以创建一个Canoo WebTest 测试Jsp页面。
之所以说这一步是可选的,因为可以手工通过浏览器进行完成同样的测试。
可以通过下面的URLs测试adding,editing和saving一个Person的操作。
Add - http://localhost:8080/appfuse/editPerson.html
.
Edit - http://localhost:8080/appfuse/editPerson.html?id=1
(make sure and run ant db-load first).
Delete - Use the edit link above and click on the Delete button.
Save - Click edit
and then click the Save button.
Canoo测试非常灵活,只需要简单在XML文件增加配置项就可以完成。为了测试add、edit、save和delete操作,打开test/web/web-tests.xml文件并且加入下面的XML代码。你可以注意到这个代码片断有一个PersonTests目标任务运行所有的相关测试。
我使用了CamelCase任务命名方式(不同于传统的小写下划线分割的命名方式) 因为你可以采用-Dtestcase=Name的键入方式, 我已经习惯了在采用CamelCase命名规则运行JUit测试。
|
完成上面的工作后,可以在Tomcat启动的情况下运行 ant test-canoo -Dtestcase=PersonTests 或者在Tomcat没有启动的情况下运行 ant test-jsp -Dtestcase=PersonTests(如果你希望由Ant来启动/停止 tomcat)。为了在所有的Canoo测试中包括PersonTests,在"run-all-tests"依赖任务中增加这个任务。
此时Cacoo在客户端没有纪录日至。如果你想加入日志你可以在</canoo> 和 </target>之间在目标任务的底部加入下面的内容。
<loadfile property="web-tests.result"
srcFile="${test.dir}/data/web-tests-result.xml"/>
<echo>${web-tests.result}</echo>
BUILD SUCCESSFUL
Total time: 10 seconds
下一部分 第四部分: 增加校验功能和列表页面 - 增加一个验证personForm的firstName和lastName为必填项的校验逻辑,并且增加一个列表面显示数据库中所有的person记录。
第四部分: 增加校验和列表页面 - 增加Person对象的校验逻辑保证firstName和lastName是必填字段并且加入列表页面显示数据库中所有的person纪录。
阅读这部分指南请先阅读 第三部分: 创建Webwork Action和JSP.
这部分指南将向你展示如何使用Webwork的校验框架加入校验逻辑。并且将使用Display Tag Library 标签库(Tag Library)创建列表页面显示数据库中的所有Person。
我会用这种格式说明我在 实际过程 中的操作。
[1] 创建包含了校验规则了Person-validation.xml文件
[2] 查看加入了校验的JSP页面并且进行测试
[3] 在DAO、ManagerTest类中加入testGetPeople方法
[4] 在PersonDAO和Manager类中增加 getPeople 方法
[5] 在Action Test中加入testSearch方法
[6] 在Action中加入search 方法
[7] 创建personList.jsp和Canoo test
[8] 在菜单中加入链接
创建包含了校验规则了Person-validation.xml文件 [#1]
为了利用WebWork校验框架实现数据校验有两件事情要做,第一是创建一个validation.xml文件,第二是在需要进行校验的action中加入一个校验interceptor引用。
WebWork允许两种类型的校验 —— per-action和model-based。因为所有的Action对Person引用都要使用相同的校验规则,所以本文将使用model-based类型的校验。
在src/dao/**/model目录下创建Person-validation.xml文件并加入下列内容:
|
在ApplicationResources_*.properties文件中的"errors.message" 键值使用字段的"name"属性以实现国际化。如果不需要提供对i18n的支持可以直接对<message>元素中指定显示内容。
|
现在可以配置PersonAction使用visitor validation 。为了实现这个目标,在PersonAction目录下创建一个PersonAction-validation.xml文件。加入下面的内容:
|
糟糕的是,WebWork没有提供一个透明机制读取Person-validation.xml文件并且标记在UI上标记哪个字段时必须的。AppFuse的Struts和Spring版本使用LabelTag实现了这个目标,不过他们也只是实现了一个普通的校验。我希望有人能够为WebWork提供相同的功能实现。同时JSP tags "required" 属性实际上没有对你所指定的校验规则作任何事情,仅仅是在加入对应的字段后面加入了一个星号而已。
注意: 在AppFuse里面提供的SpringObjectFactory的客户端model-based校验规则无法工作
。并且我认为Webwork提供的客户端校验需要增加一些特性,例如:允许取消
和 在一个对话框中显示所有的错误信息
。因为这些原因在AppFuse+webwork环境中只使用了服务器端校验。如果你希望使用,你可以了解在这里了解更多
我使用客户端校验遇到的问题
当然,也可以使用per-action校验。只需要拷贝Person-validation.xml文件到"webapp.action"包中并且把它重命名为PersonAction-validation.xml。
为了使在"savePerson" 操作中我们新加入的校验规则发挥作用,我们要把原来在"validator"属性上的注释去掉。确定最后在web/WEB-INF/classes/xwork.xml文件的"savePerson" 部分包含以下内容:
|
说明:在Appfuse中使用的validationStack和WebWork自带的有些不同,更多的信息可以在WebWork's JIRA 中查找。
查看加入了校验的JSP页面并且进行测试 [#2]
现在保存所有的文件。为了测试加入了校验后的JSP,运行ant db-load deploy,启动Tomcat并且在浏览器中输入 http://localhost:8080/appfuse/editPerson.html?id=1 。
如果删掉了firstName或者lastName字段的值并点击save按钮,你将看到错误提示信息:
为了创建一个List页面(或者说是master页面),我们需要穿件一个方法返回person表中的所有行。我们首先在PersonDAOTest和 PersonManagerTest类中创建测试方法。通常把这个方法命名为getEntities (例如getUsers),你也可以使用 getAll 或者 search —— 这其实是同一类问题。
打开test/dao/**/dao/PersonDAOTest.java文件加入testGetPeople方法:
|
我在getPeople方法中传入一个person对象是想在以后方便加入过滤(filtering)处理 (基于person对象中的属性值)。在getPeople()方法中应该说明这个参数是可选的。
现在打来test/service/**/service/PersonManagerTest.java文件加入 testGetPeople 方法。
|
为了这两个类能够通过编译,需要在PersonDAO和PersonManager接口中加入getPeople()方法并且加入实现。
在PersonDAO和Manager类中增加 getPeople 方法 [#4]
打开src/dao/**/dao/PersonDAO.java文件并且加入getPeople()方法说明:
|
现在在src/service/**/service/PersonManager.java文件中加入同样的方法。保存所有的文件并且在tests类中调整imports类。接下来在实现类中实现getPeople()方法。打开src/dao/**/dao/hibernate/PersonManager.java文件加入下面的代码:
|
你可以注意到现在没有对person参数作任何处理。仅仅是占了个位置 —— 在以后你可以依靠它的属性值使用Hibernate's查询语言 (HQL) 或者使用 Criteria Queries 加入filter。 一个使用Criteria Query的示例:
|
在src/service/**/impl/PersonManagerImpl.java中实现getPeople()方法:
|
保存所有的变更,运行下面的测试方法:
ant test-dao -Dtestcase=PersonDAO
ant test-service -Dtestcase=PersonManager
如果一切正常可以在web层加入读取所有人员信息的功能实现了。
在Action Test中加入testSearch方法 [#5]
打开test/web/**/action/PersonActionTest.java文件加入下面的方法:
|
只有在PersonAction中加入 getPeople() 和 list() 方法这个类才能通过编译。
在Action中加入list()和getPeople()方法 [#6]
打开src/web/**/action/PersonAction.java 加入list() 方法。在此之前加入"people"变量和 getPeople() 方法。
我使用 UserAction.search() 作为模板编写这个方法的代码。
|
运行 ant test-web -Dtestcase=PersonAction 进行测试。 好!
BUILD SUCCESSFUL
Total time: 10 seconds
创建personList.jsp和Canoo test [#7]
打开web/pages目录下的personList.jsp文件进行编辑。在文件顶部有一个<ww:set>标签用来展示 "people" 信息。你需要把这个引用的值从"persons" 改成 "people"。
|
另一个你需要改变的地方是表单上的人的复数形式单词。示例程序产生的名字是 "persons" 这个正确的表示方法是 "people"。在大约30行的位置,你可以找到下面这一行
<display:setProperty name="paging.banner.items_name" value="persons"/>
改变成:
<display:setProperty name="paging.banner.items_name" value="people"/>
最后在to web/WEB-INF/classes/ApplicationResources_en.properties文件中加入title和heading 键值 (personList.title和 personList.heading) :
# -- person list page --
personList.title=Person List
personList.heading=All People
需要注意的是,personList.title将会出现在浏览器的标题栏中,而personList.heading将会显示在页面中作为标题:
还要在在web/WEB-INF/classes/xwork.xml文件中加入一个新的"people" action 一边能够在URL中调用list()方法:
|
这时可以运行ant clean deploy, 启动Tomcat把浏览器转到http://localhost:8080/appfuse/people.html 查看这个显示列表的页面了 At this point, you should be able to run ant clean deploy, start Tomcat and view this page in your browser at http://localhost:8080/appfuse/people.html .
现在有了一个列表显示页面,让我们改变在新增和删除了一个Person后显示这个页面。在web/WEB-INF/classes/xwork.xml文件中,改变 savePersons的 "input" 和 "success" 结果指向"people.html"。 你还需要改变Canoo tests "AddPerson" 和 "DeletePerson" 任务脚本 。打开 test/web/web-tests.xml 文件定位到 "AddPerson" 任务中下面这一行:
<verifytitle stepid="Main Menu appears if save successful"
text="${webapp.prefix}${mainMenu.title}"/>
修改成:
<verifytitle stepid="Person List appears if save successful"
text="${webapp.prefix}${personList.title}"/>
然后定位到 "DeletePerson" 任务的下面这一行:
<verifytitle stepid="display Main Menu"
text="${webapp.prefix}$(mainMenu.title}"/>
修改成:
<verifytitle stepid="display Person List" text="${webapp.prefix}${personList.title}"/>
为了测试列表页面的工作,在test/web/web-tests.xml中创建一个新的JSP测试:
|
你也许希望加入 "SearchPeople" 测试任务到 "PersonTests" 测试任务中去,以便能够和其他相关测试对象一起被测试。
|
现在可以运行 ant test-canoo -Dtestcase=SearchPeople (或者运行 ant test-jsp 如果Tomcat没有运行)。如果结果是"BUILD SUCCESSFUL"就大功告成了!
在菜单中加入链接 [#8]
最后一步把list, add, edit和delete功能显示给用户访问最简单的办法是在web/pages/mainMenu.jsp文件中加入新的链接:
|
menu.viewPeople是定义在web/WEB-INF/classes/ApplicationResources_en.properties中的一个键。
menu.viewPeople=View People
另一个办法是改变web/WEB-INF/menu-config.xml加入下面的内容:
|
确定上面的XML在 <Menus> 标签内,但是没有在另外一个<Menu>里面。然后在web/pages/menu.jsp中加入新菜单 —— 现在看起来是下面的样子:
|
现在运行ant clean deploy 启动Tomcat在浏览器中输入http://localhost:8080/appfuse/mainMenu.html ,你可以看到下面的界面
注意在页面左边有一个新的链接(由 mainMenu.jsp产生) ,同时在右边的菜单上也有一个新链接(由 menu.jsp产生)。
现在已经完成了一个完整的master-detail的页面开发过程!现在可以运行所有的测试而看不到任何错误了!可以进行全面的测试, 停止tomcat并且运行 ant clean test-all。 这个命令会运行工程里面所有的单元测试。需要提醒注意的是,应该可以正常的利用ant setup-db setup-tomcat test-all 来完成AppFuse程序的环境设置和全面测试。此时你可以参考一些更复杂的示例程序 - Struts Resume.
多么美好的一天!
BUILD SUCCESSFUL
Total time: 4 minutes 15 seconds