Grails + EJB 领域模型教程

阅读数:1630 2007 年 8 月 26 日

Ruby on Rails 不断地受到软件工程世界的关注,但企业依旧对其表示怀疑。为什么会这样?我们怀疑:“构建于脚本语言之上的框架,怎能适合我的企业应用?!”针对 Ruby on Rails,典型的论调就是缺少对企业服务(如分布式事务、消息传递等)的支持。对很多企业而言,如果平台没有这些服务,那么它将不可能被考虑。

Grails旨在解决那些关注点,并证明快速应用开发(RAD)对企业是可行的。Grails 建构于Groovy之上,提供了与 Java 的无缝集成。它能直接访问你的业务所依赖的那些企业服务,同时为你的工具集增添强大的动态语言结构。

作为展示它企业集成能力的令人印象深刻的一个例子,Grails 可让你快速而简单的基于已有 EJB3 实体 Bean 构建一个 Web 应用。但是,它的能力并不是仅此而已。Grails 大幅增强了你的实体 Bean 的能力,而且这些完全是动态做到的,没有更改任何你的 EJB 源码。Grails 对象关系映射(GORM)建立在 Hibernate3(最终将提供对 Java 持久化 API 的支持)之上,利用 Groovy 的元对象协议(MOP)为你的不同静态实体 Bean 增加了各种方便的动态方法。这些方法不仅能从 Grails 和 Groovy 访问,而且你的 Java 代码同样能访问它们!我们瞬间就拥有了 JEE/EJB3 的企业级能力和 RAD Web 应用开发的全部好处!

那么,让我们看看基于 EJB3 实体 Bean 构建 Grails 应用要做哪些工作。在以下步骤中,我们将创建一个新的 Grails 应用,将实体 Bean 导入到应用,为实体 Bean 产生快速构建缺省 Web 界面的脚手架代码(scaffolding),然后再探索 Grails 为实体 Bean 增加的一些方便的动态方法。

首先,我们需要从一个 EJB3 应用开始。(Grails 并不需要你以一个 EJB3 应用作为起点。但是,本文的一般性假设是你有兴趣将 RAD Web 开发并入你的 EJB3 项目。)假设我们有一些 EJB3 实体 Bean,它们代表一个公司里的员工(EmployeeBean)和分配给员工的计算机(ComputerBean)。(如果遇到任何问题,请参见资源小节。你将在那儿找到该 Grails 应用的完整源代码——一个使用这些实体 Bean 的简单 JEE 应用——以及其它有用的东西。)

以下是支持我们实体 Bean 的两张表。(例子中,我们将使用MySQL 5.0。你可以使用这个脚本创建一个名为 ejb3example 的新数据库,它同时将生成这些表。)

接下来,让我们快速地查看一下公司里忙碌地工蜂们及他们的硬件。

构建与这些数据交互的 Web 用户界面,并和已有代码集成要花多长时间呢?它这不应该花费太多的时间,但是,我们肯定已经接受了这样的观点,即这个过程需要重大的努力。使用 Grails,无需如此。

步骤 1 —— 安装 Grails

因为 EJB3 依赖JDK 5,所以你需要确保 JDK 5 已经安装,并将你的 JAVA_HOME 环境变量指向 JDK 5 的安装目录。

按照这些快速步骤,在你的系统上安装 Grails。(本文使用 Grails 0.2.1,它是撰写文章时的最新稳定发布。译注:当前的版本是 0.5.6。)(如果你在使用 *nix 系统,如遇安装问题,请检查这个线索。)

步骤 2 —— “Hello Grails!”

  1. 在命令提示符下,进入你想创建 Grails 应用的目录。然后输入 grails create-app。当询问应用名时,输入ejb3_grails
    jMac:~/dev jason$ grails create-app

    ...

    create-app:

    [input] Enter application name:

    ejb3_grails

    ...

    BUILD SUCCESSFUL

    Total time: 4 seconds
  2. 为了验证环境正确,启动应用。进入刚刚创建的应用目录,然后输入grails run-app启动应用。
    jMac:~/dev jason$ cd ejb3_grails jMac:~/dev/ejb3_grails jason$ grails run-app ... run-app:watch-context: 
  3. 现在,应用正等待我们的请求。打开浏览器,输入http://localhost:8080/ejb3_grails/,你应该看到以下欢迎你进入 Grails 的友好信息。

步骤 3 —— 导入实体 Bean

  1. Grails 预装了 HSQLDB,但因为我们使用的是 MySQL,我们需要一些快捷的步骤,来告诉 Grails 如何与我们的数据库进行交流。首先,从http://www.mysql.com/products/connector/j/下载 MySQL 的 Java 驱动。我选择的是当前可用于产品阶段的产品,在本文撰写的时候,它的版本是 3.1.13。
  2. 打开 zip 文件,将其中的mysql-connector-java-3.1.13-bin.jar解压到你的 Grails 应用的 lib 目录——本例中,是ejb3_grails/lib。(注意:JAR 文件确切的名字会跟据你下载驱动的版本不同而变化)。
  3. 现在,我们准备告诉 Grails 在哪儿找到我们的数据库。用你喜爱的编辑器打开ApplicationDataSource.groovy,按以下内容修改。你可以在ejb3_grails/grails-app/conf/中找到这个文件。(注意:你需要改变用户名口令以适应你 MySQL 帐号。)
    import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration

    class ApplicationDataSource {



    def configClass = GrailsAnnotationConfiguration.class

    boolean pooling = true

    //String dbCreate = "create-drop" // one of 'create', 'create-drop','update'

    String url = "jdbc:mysql://localhost/ejb3example"

    String driverClassName = "com.mysql.jdbc.Driver"

    String username = "ejb3example"

    String password = "ejb3example"

    }

    除了指明链接设置,我们还需要定义configClass成员,以允许 Grails 支持在实体 Bean 中使用标注。

    最后,我们需要给dbCreate设置加上注释。这个设置允许 Grails 在运行时使用你的领域对象同步更新数据库模式。尽管它是个强大的选项,但是在这个例子中我们并不需要。通过把这个设置注释掉,我们指示 Grails 保持模式的原样。

  4. 接下来,我们需要将实体 Bean——EmployeeBeanComputerBean——复制到我们的 Grails 工程。Grails 会在src/java目录中查找 Java 类。请确保创建与这些类的包相匹配的、完整的目录结构。

    jMac:~/dev/ejb3_grails/src/java/com/jasonrudolph/ejb3example/entity jason$ ls

    ComputerBean.java EmployeeBean.java

    在很长时间内,你将需要确保这些文件与这些类的正式副本保持同步(它们在你的 JEE 工程源码树中)。使用构建脚本,你可以很容易地做到这点。即,在构建时,将这些文件从 JEE 工程中复制到 Grails 工程中。

  5. Grails 允许我们与任何 Java 类工作,但是我们需要让 Grails 给予这些特殊的 Java 类(也就是我们的实体 Bean)特别的对待。我们要让 Grails 将这些类认为是我们的领域类,并提供 Grails 领域类所拥有的 ORM 和动态方法的全部好处。要做到这些,我们需要在应用的hibernate目录增加如下的hibernate.cfg.xml文件,注册这些类。
    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE hibernate-configuration PUBLIC

    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

    <hibernate-configuration>



    <session-factory>

    <mapping package="com.jasonrudolph.ejb3example.entity" />

    <mapping class="com.jasonrudolph.ejb3example.entity.EmployeeBean" />

    <mapping class="com.jasonrudolph.ejb3example.entity.ComputerBean" />

    </session-factory>

    </hibernate-configuration>



步骤 4 —— 产生脚手架代码

现在,我们准备真正开始蓄势待发了。我们至今为止所做的大多数事情并没有被免去。(不论你的框架是多么的聪明,你总得告诉它在哪儿找到数据库。)然而,为用户界面构造一个好的、功能性的起点不再是手工活儿了。

  1. 确保你在工程的根目录下——在我们的例子中,它是ejb3_grails。然后,输入grails generate-controller。当询问领域类名时,输入我们第一个实体 Bean 的全限定类名——com.jasonrudolph.ejb3example.entity.EmployeeBean.
    jMac:~/dev/ejb3_grails jason$ grails generate-controller  ... input-domain-class:     [input] Enter domain class name: com.jasonrudolph.ejb3example.entity.EmployeeBean ... [java] Generating controller for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean]      [java] Controller generated at ./grails- app/controllers/EmployeeBeanController.groovy  BUILD SUCCESSFUL Total time: 10 seconds 
  2. 既然有了控制器(controller),让我们产生相应的视图(view)。输入grails generate-views。当询问领域类名时,输入com.jasonrudolph.ejb3example.entity.EmployeeBean
    jMac:~/dev/ejb3_grails jason$ grails generate-views ... input-domain-class:     [input] Enter domain class name:  com.jasonrudolph.ejb3example.entity.EmployeeBean ... [java] Generating views for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean]      [java] Generating list view for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean]      [java] list view generated at /Users/jason/dev/ejb3_grails/./grails- app/views/employeeBean/list.gsp      [java] Generating show view for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean]      [java] Show view generated at /Users/jason/dev/ejb3_grails/./grails- app/views/employeeBean/show.gsp      [java] Generating edit view for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean]      [java] Edit view generated at /Users/jason/dev/ejb3_grails/./grails- app/views/employeeBean/edit.gsp      [java] Generating create view for domain class [com.jasonrudolph.ejb3example.entity.EmployeeBean]      [java] Create view generated at /Users/jason/dev/ejb3_grails/./grails- app/views/employeeBean/create.gsp  BUILD SUCCESSFUL Total time: 11 seconds 
  3. 对其他实体 Bean(即com.jasonrudolph.ejb3example.entity.ComputerBean),重复以上过程,以产生相应的控制器和视图。

  4. 现在,让我们运行应用,看看通过以上那么丁点儿努力,我们得到了什么。输入grails run-app。打开浏览器,进入http://localhost:8080/ejb3_grails/

  5. 很好,我们现在已有了两个控制器。并且,正如这儿的文字所建议的,我们最终会使用我们自己自定义的首页面替换它。至于现在,让我们转到EmployeeBeanController

这儿,我们看到了大多数想要的东西。显然Computers这一列需要进行些调整,然而在别的方面,这页只需作一些装饰性的修改就可以用了。

让我们花些时间参观一下这个应用,看看我们现在得到了什么。试试创建新员工、编辑员工,接下来(如果你觉得特别有权势)终结某个可怜虫。对于计算机管理,试试类似的特性。在体验过程中,记下你想改变以及需要修改的地方。同时,一定要考虑那些已经满足了你需要的东西。它们是你“不劳而获”的内容!

......

完工了?很好,以下是一张我们所需东西的清单,然后我们可以谈谈那些“有则更好”(nice-to-have)的条目。

  • 关系管理——很明显,脚手架在这个领域做了尝试,但还是不满足我们的需要。幸运的是,这非常容易修补(瞧瞧,即使是 Ruby on Rails 脚手架也没有给你免费的关系管理。)
  • 验证——它只是被遗漏了。非常简单。我们也可以很快地添加它。

就是它了。如果我们能实现正确的关系管理和验证,我们将获得一个功能齐全的 Web 应用来管理我们的实体 Bean。在这之后,其余的就是小菜一碟。(尽管我们确定要在结束之前尝尝一些可口的小菜。)

步骤 5 —— 增加关系管理

关于关系管理,我们对应用有哪些期望?嗯,要我说,我们应该能……

  • 查看分配给某个员工的所有计算机。
  • 查看单个计算机的细节(包括它的分配状况)。
  • 能增加、修改和删除计算机(包括它的分配状况)。

准备好了吗?让我们开始吧。

  1. 员工页列出计算机没有什么意义,因此让我们把这列移除。打开grails-app/views/employeeBean/list.gsp,并移除该列。现在,刷新浏览器,验证修改。

  2. 接下来,点击显示(Show),查看员工的细节。

    最少,我们需要整理显示每个计算机的文本。但是,可能我们根本就不愿直接在这个页面上看到计算机。与其在该页显示计算机,不如包含一个指向该员工的计算机列表的链接。

    打开该页模板(即,grails-app/views/employeeBean/show.gsp),移除显示当前员工计算机列表的行。接着增加以下行,链接到显示该员工的计算机列表的单独页面。

    <tr class="prop">

    <td colspan="2" align="left" class="name">

    <g:link controller='computerBean' action='showComputersByEmployee'

    id='${employeeBean.id}' >Show Computers</g:link>

    </td>

    </tr>

    此处,我们使用了Grails 标签库link标签将产生指向ComputerBeanController的链接,并调用一个我们需要定义的新动作,showComputersByEmployee。该链接还包含了所请求员工的 ID。

    让我们刷新浏览器,看看变化。

    很好,我们得到链接了。现在,我们需要为链接定义一个新动作。在编辑器中打开grailsapp/controllers/ComputerBeanController.groovy,因为新动作将通过员工查找计算机,我们首先需要增加引用EmployeeBean 类的 import 语句。

    import com.jasonrudolph.ejb3example.entity.EmployeeBean

    接着,我们增加新动作。

    def showComputersByEmployee = {

    render(view:'list', model:[ computerBeanList:

    ComputerBean.findAllByEmployeeBean(EmployeeBean.get(params.id)) ])

    }

    这个动作让我们好好见识了一下 Groovy(和 Grails)的能力,通过简单的几行代码,你的表现力究竟有多大。在以上几行代码中,我们告诉 Grails,任何showComputersByEmployee的调用应该......

    • 使用params.id从 request 中获得员工
    • 使用EmployeeBean#get方法获得员工 ID 对应的员工
    • 使用ComputerBean#findAllByEmployeeBean方法找出与员工相关的所有计算机
    • 将结果放入名为computerBeanList的对象
    • 展现(render)视图,在一个名为list的模板中,使用 computerBeanList 对象作为模型。

    记得曾在EmployeeBean中定义 get 方法,以及ComputerBean中定义findAllByEmployeeBean方法吗?没有?答对了。这些方法只是 Grails 为你的领域对象所提供的众多动态方法的一小部分而已。之后,我们将更多的探讨这些条目。现在,我们准备点击显示计算机链接了。

    我们越来越接近完工了。我们仍然需要修改Employee Bean列中的文本。我们需要在此显示些更让人明白点儿的东西。

    同样,我们可能更愿意标出这些计算机所属的员工。我们当前重用了list模板(作为ComputerBean的脚手架代码的一部分产生),而那个模板最初是为了列出所有计算机而设计的。另一方面,我们总能定义一个单独的模板,它用于显示计算机的子集,或能使list模板更动态一些以支持两个场景。就现在而言,我们将去掉这个特性,将其作为“有则更好”,而非必要的特性。

    我们应该完成了管理员工所需的所有改变。现在,我们只需整理计算机管理的特性。

  3. 既然我们就在这儿,让我们整理一下计算机list模板。替换Employee Bean列的当前文本,改为显示员工的网络 ID。打开grails-app/views/computerBean/list.gsp。找到在展现Employee Bean列文本的 Groovy 脚本……
    ${it.employeeBean}

    ……将它改为显示员工的网络 ID。

    ${it.employeeBean.networkId}

    刷新浏览器,让我们看看它的样子。(Employee列在Brand列和Model列之间显得有些不合适,所以我调换了一下它们的位置。不愿这样做也可以。)

    list模板完成了,现在该show模板了。

  4. 点击显示(Show)链接,并访问我们需要改变的页面。

    看起来,我们只需改变员工链接的文本就行了。对此,我们完全赞同。那么,打开grailsapp/views/computerBean/show.gsp,并找到展现当前链接文本的那段脚本。

    ${computerBean?.employeeBean}

    就象我们为list模板所做的那样,改变代码以显示员工的网络 ID。

    ${computerBean?.employeeBean.networkId}

    让我们再次刷新浏览器,并验证我们的改变。(同样,Employee行在Brand行和Model行之间显得有些不合适。因此,只要觉得合适,你可以随意重新调整这些行。)

  5. 让我们转到编辑(edit)功能。

    我们着手查看这儿的一个主题。我们需要改变选择框,以提供一组网络 ID 的列表。在grails-app/views/computerBean/edit.gsp中找到 <g:select> 标签,把它修理成以下内容:

    <g:select optionKey="id"

    from="${com.jasonrudolph.ejb3example.entity.EmployeeBean.list()}"

    name='employeeId'

    optionValue='networkId'

    value='${computerBean.employeeBean?.id}'>

    </g:select>

    通过给标签增加optionValue参数,选择框中的文本会更具有含义。

    现在,视图被修正了,但是更新(update)功能还需要除了视图之外的一点努力。我们同样需要增强控制器(即ComputerBeanController.groovy)。如果用户改变关联了计算机的员工,我们需要确保正确地持久化这些关联的变化。换句话说,我们需要从当前的员工去除关联,而将它分配给新的员工。这个增强型的更新方法需要一点额外的代码行。

    def update = {

    def computerBean = ComputerBean.get( params.id )

    if(computerBean) {

    if (computerBean.employeeBean) {

    computerBean.employeeBean.computers.remove(computerBean)

    }

    computerBean.properties = params

    def employeeBean = EmployeeBean.get(params.employeeId)



    employeeBean.computers.add(computerBean)

    computerBean.employeeBean = employeeBean

    if(computerBean.save()) {



    redirect(action:show,id:computerBean.id)

    }

    else {

    render(view:'edit',model:[computerBean:computerBean])

    }

    }

    else {

    flash.message = "ComputerBean not found with id ${params.id}"

    redirect(action:edit,id:params.id)

    }

    }

    保存你的改变,我们准备试验它了。让我们改变这台计算机的品牌——现在是联想(Lenovo)了——并将它重新分配给 John Doe。

  6. 当然,我们也需要能够给我们的库存增加新的计算机。因此,让我们点击新建计算机(New ComputerBean)。

    我们经过上次更改编辑(Edit)页之后,我们已具有很好的资格来整理这一页。我们需要对选择框做些相同的调整。打开grails-app/views/computerBean/create.gsp,调整<g:select>标签,并刷新你的浏览器。

    <g:select optionKey="id"

    from="${com.jasonrudolph.ejb3example.entity.EmployeeBean.list()}"

    name='employeeId'

    optionValue='networkId'

    value='${computerBean.employeeBean?.id}'>

    </g:select>

    正如我们看到的编辑(edit)功能,我们需要对控制器进行轻微的增强。当我们创建新计算机时,我们需要在保存它之前分配给一个员工。编辑ComputerBeanController.groovy,以包含这个被更新的保存(save)方法。

    def save = {

    def computerBean = new ComputerBean()

    computerBean.properties = params

    def employeeBean = EmployeeBean.get(params.employeeId)



    employeeBean.computers.add(computerBean)

    computerBean.employeeBean = employeeBean

    if(computerBean.save()) {



    redirect(action:show,id:computerBean.id)

    }

    else {

    render(view:'create',model:[computerBean:computerBean])

    }

    }

    回到浏览器,我们准备创建一个新计算机了。填写空的域,并创建。

    现在,Jane 有一台崭新的膝上电脑了。

  7. 7. 最后,只剩下删除(delete)功能了。这次我们不需要改变任何模板。我们只需要给控制器增加一行。当我们在ComputerBeanController.groovy中删除一台计算机时,我们同样需要移除对应的计算机与员工的关联。以下的第四行代码负责这些。

    def delete = {

    def computerBean = ComputerBean.get( params.id )

    if(computerBean) {



    computerBean.employeeBean.getComputers().remove(computerBean)

    computerBean.delete()

    flash.message = "ComputerBean ${params.id} deleted."

    redirect(action:list)

    }

    else {

    flash.message = "ComputerBean not found with id ${params.id}"

    redirect(action:list)

    }

    }

    看起来 Jane 的新 MacBook 被回收了。我们应该删除它吗?

    此时,我们完成它了!使用快捷的几步,我们现在已经有了一个功能完备的 Web 应用,它构建于我们的实体 Bean 上。当然,它还需一些打磨。但至少,作为一个可工作的原型,它已经足够了。尽管仍需改良,但它是个完全能发展成完美产品的一个原型。

步骤 6 —— 定义验证规则

我们近期肯定需要的一件事就是某种验证逻辑。幸运的是,Grails 提供了非常方便的声明性验证机制。对于每个实体 Bean,我们仅需增加一个 Groovy 脚本来定义合适的约束。根据惯例,Grails 以领域类名 +“Constraints”为名字(如EmployeeBeanConstraints.groovy),去查找脚本。

那么,让我们为每个实体 Bean 来定义约束吧。最少,我们需要约束匹配我们的数据模型。我们也会加入一些基本的业务验证规则。如,数据库要求网络 ID 长度不超过 8 个字符,但是,我们也会加强我们的业务规则,要求网络 ID 的长度必须不小于 6 位字符。(译注:即长度 >=6 且 <=8)

Grails 在领域类相同的目录下查找约束声明。对于EmployeeBean类,我们需要创建名为EmployeeBeanConstraints.groovy的文件,并将它放在ejb3_grails/src/java/com/jasonrudolph/ejb3example/entity/。文件应该包含以下规则:

constraints = {

networkId(length:6..8,blank:false,unique:true)

firstName(maxLength:20,blank:false)

lastName(maxLength:20,blank:false)

startDate(nullable:false)

}

接下来,定义用于ComputerBean类的约束。同样,文件也要在相同的目录下,命名为ComputerBeanConstraints.groovy.

constraints = {

serialNumber(maxLength:20,blank:false)

brand(maxLength:20,blank:false)

model(maxLength:20,blank:false)

}

为了使这些约束生效,我们需要重启应用。在启动应用的控制台中,输入Control-C结束应用。然后,再输入grails run-app,我们准备开始测试验证了。

现在,如果我们试图偷偷在一个新员工中输入一个无效网络 ID,应用应该会拒绝它。

很自然,我们需要修改错误消息,使其更具业务含义,技术性更小一些。Grails 提供了一个消息资源文件,它可让你自定义错误消息

我们定义的约束只代表了 Grails 提供验证的小部分例子。请查看 Grails 文档以获得全部可用的约束

步骤 7 —— 获得动态能力

至今所做的任何事情都向我们展示了,我们可以比曾经所想象的更快的速度来构建 Web 应用,并且我们已看到了这个即将到来的框架一些令人印象深刻的特性。这才是真正魔术的开始!还记得当我们增加查看用户所分配的所有计算机功能的时候曾简单地提过动态方法吗?让我们更深入的看看这个特性吧。

一旦我们具备了基本的 CRUD 功能,接下来第一个要求是,“我需要一种通过 ____ 来查看所有员工的方法”,或“从序列号 ____ 开始显示所有由 ____ 制造的计算机”。这些都是合理的要求,但是一般情况下构建这些简单的报表需要花费多少努力呢?通常这需要在某个会话 Bean 中增加一个新方法,在我们 Web 框架的配置文件中增加一个新映射,可能甚至需要些 SQL,等等。利用 Grails,我们不仅无需那些东西就可完成,而且还为快速适应下一个变化作好了准备。

对于初学者,我们假定需要能够通过 last name 找到所有员工。我们将给员工列表(Employee List)页面增加新的菜单项,它将带我们到新的查询页。在grails-app/views/employeeBean/list.gsp中的菜单部分增加下列代码。

<span class="menuButton">

<g:link action="search">Search Employees</g:link>

</span>

点击这个菜单项将调用控制器中名为 Search 的新动作。除了展现搜索输入页,我们不需要让这个动作干别的。因此,在EmployeeBeanController.groovy中增加一个空方法来接收请求。

def search = {

}

这个空动作告诉 Grails,search 是该控制器的有效动作,当 Grails 收到关于该 action 的请求时,它将在grails-app/views/employeeBean/中简单地查找同名(即search.gsp)模板,并展现它的内容。

既然,我们事实上只是需要一个用于员工的包含输入框的简单表单,我们可以将create.gsp作为一个好的起点。当我们完成时,新的search.gsp模板应该是如下的样子:

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>



<meta name="layout" content="main" />

<title>Search Employees</title>

</head>

<body>

<div class="nav">



<span class="menuButton">

<a href="${createLinkTo(dir:'')}">Home</a></span>

<span class="menuButton">

<g:link action="list">EmployeeBean List</g:link></span>

</div>



<div class="body">

<h1>Search Employees</h1>

<g:form action="showSearchResults" method="post" >

<div class="dialog">

<table>



<tr class='prop'>

<td valign='top' class='name'>

<label for='lastName'>Last Name Like:</label>

</td>

<td valign='top' class='value'>



<input type='text' name='lastName' value='' />

</td>

</tr>

</table>

</div>

<div class="buttons">



<span class="formButton">

<input type="submit" value="Search"></input>

</span>

</div>

</g:form>

</div>



</body>

</html>

如果你认为我们不应该重复这么多的包装器内容(如 <head> 标签等),那么你是对的。Grails 提供了各种布局机制来避免这种重复。(我们将把这作为读者的一个练习。)同时,我们的搜索页就绪了。(尽管 Grails 通常提供这种变化的动态部署,但一些读者已经报告,为了能成功访问搜索页需要重启应用。要这么做,在启动应用的控制台中输入 Control-C。然后输入 grails run-app,然后进入搜索页。)

search.gsp中,你会注意到,我们指定表单应该将所有请求发往一个新的名为showSearchResults(够直白的)的动作。因此,我们需要在EmployeeBeanController.groovy中定义这个新的动作。

def showSearchResults = {

render(view:'list', model:[ employeeBeanList:

EmployeeBean.findAllByLastNameLike("%" + params.lastName + "%") ])

}

因为我们只想显示匹配的员工列表,所以我们可安全地使用已有的list.gsp模板。这是 Grails 提供的另一个以简洁命令完成重要功能的例子。所有给showSearchResults的请求将……

  • 使用params.lastName获得搜索条件
  • 用员工匹配 lastName 获得EmployeeBean对象列表
  • 把结果放入名为employeeBeanList的对象
  • 展现视图,使用 list 模板,使用employeeBeanList对象作为模型。

当我们点击搜索,我们肯定会得到匹配结果。

当然,如果我们能使用 last name 来搜索员工,为什么不能用 first name 完成同样的事?在搜索页,我们可以很容易的增加一个新的输入框。

<tr class='prop'>

<td valign='top' class='name'>

<label for='lastName'>First Name Like:</label>

</td>



<td valign='top' class='value'>

<input type='text' name='firstName' value='' />

</td>

</tr>

非常简单,但是对控制器需要做哪些改动呢?非常少,取代findAllByLastNameLike方法,现在调用findAllByLastNameLikeAndFirstNameLike

def showSearchResults = {

render(view:'list', model:[ employeeBeanList:

EmployeeBean.findAllByLastNameLikeAndFirstNameLike("%" + params.lastName + "%",

"%" + params.firstName + "%") ])

}

Grails 为你所能想到的每个查询组合自动提供动态查找器!不信试试。来自以上各域的搜索,只是我们期望给查询的变更。

步骤 8 —— 构造自己的条件

随着时间的流逝,你可能发现需求变得复杂起来。我们如何处理需要组合各种条件集合的查询呢?如,想象我想查找所有的员工,根据……

  • 网络 ID 使用大小写敏感,部分匹配一些值,或者
  • first namelast name精确匹配它们各自的条件。

这种查询已经超出了我们刚刚看到的动态查找器的能力。不要担心。这不意味着你的应用必须承担任何额外的复杂性。Grails 提供了非常灵活的Hibernate Criteria Builder以满足需要,并且不以牺牲我们至今已看到的任何功能为代价。

首先,让我们修改搜索页以捕获用于新需求的输入。如果我们编辑search.gsp,并使用以下域替换当前的输入域,我们将会获得所期望的东西。

<tr class='prop'>

<td valign='top' class='name'><label for='lastName'>Network ID Like:</label></td>

<td valign='top' class='value'>

<input type='text' name='networkId' value='' />

</td>



</tr>

<tr><td>-- or --</td></tr>

<tr class='prop'>

<td valign='top' class='name'><label for='lastName'>First Name Equals:</label></td>

<td valign='top' class='value'>



<input type='text' name='firstName' value='' /></td>

</tr>

<tr class='prop'>

<td valign='top' class='name'><label for='lastName'>Last Name Equals:</label></td>

<td valign='top' class='value'>



<input type='text' name='lastName' value='' />

</td>

</tr>

现在,让我们看看实际的查询逻辑。以下代码只用短短几行就满足了我们的需要(诚然,有些古怪)。(以下代码替换EmployeeBeanController.groovy中当前的showSearchResults方法)。

def showSearchResults = {

def criteria = EmployeeBean.createCriteria()

def results = criteria {



or {

ilike("networkId", "%" + params.networkId + "%")

and {

eq("firstName", params.firstName)

eq("lastName", params.lastName)

}

}

}

render(view:'list', model:[ employeeBeanList: results.adaptee ])



}

即使你以前从未使用过 Hibernate 或 Grails,你也可以很快地明白这儿所发生事情的要旨。在Groovy builder中,每个组件被作为“节点”引用。or节点告诉我们,它将匹配满足它的子节点所指定的一个或多个条件的任何行。本例中,它有 2 个子节点。

第一个子节点返回满足networkId属性上的大小写敏感的 like 操作的结果。

ilike("networkId", "%" + params.networkId + "%")

第二个子节点是and节点。它返回匹配它的所有子节点的结果。它的子节点查找那些精确匹配(eq,等于)firstNamelastName属性的行。

and {

eq("firstName", params.firstName)

eq("lastName", params.lastName)

}

将它们组合在一起,这些节点将完成我们的查询需求。

如果分组操作符(andor等。) 一开始看起来有些古怪,那是因为 Hibernate Criteria Builder 的语法与波兰式语法有些类似,该语法在操作数之前指定操作符。这肯定与 Java 相关的操作符用法大相径庭,但是在完成你的头几个查询之后,它应该变得相对直观一些。

现在,然我们实际地看看它。

这儿我们正在查找网络 ID 包含“jr”,或员工名是“John Doe”的所有员工。

接着,当我们点击搜索(Search),我们就得到结果了。

总结

相比你现在使用的方法,这个开发方法如何呢?对于这个过程中我们所增加的每一个特性,要在你的现有矿架中做相同的改动,你需要碰多少组件呢?XML 文件?DAO? 表单 / 视图类?其它?拥有那些需要维护的额外组件,你获得了什么呢?极端灵活?如果是这样,你真的需要它吗?有的商店的确要求那种灵活性,但是对于其它的 80%,一些合理的缺省设置(习惯优于配置)难道不值得提高生产力?

本次练习中,我们只写了非常少的代码,而我们获得了一个功能齐全且具弹性的应用。我们没有改变现有实体 Bean 中的任何一行。而且因为我们只写了非常少的代码就到达了这一点,我们只需维护更少的代码。明天,当需求再次改变的时候,我们将书写更少的代码。当你的应用就是敏捷的时候,只需想想你可以多快的对演变中的业务需要做出反应!

资源

作者简介

Jason Rudolph 是 Railinc 的应用架构师,他在那儿开发令火车高速贯穿北美的软件。他最近交付了一个行业审查报表和管理系统,财富 500 强铁路、铁道车辆租借公司和联邦铁路局通过它管理运行安全。Jason 的兴趣包括动态语言,轻量级开发方法学,提高开发者生产力,以及探索如何保持快乐地编程。Jason 拥有 Virginia 大学的计算机科学学位。他目前与妻子(她可承担一个功能齐全的 Web 应用,并能将它做得相当好看。)及他的狗(它比他跑的快,但还比不上松鼠)住在 Raleigh,NC。你可到http://jasonrudolph.com在线找到 Jason。

查看英文原文:Grails + EJB Domain Models Step-by-Step

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论