技术开发 频道

精通 Grails: GORM - 有趣的名称,严肃的技术

  创建一对多关系

  对于将 POGO 保存到数据库表所面临的挑战,很容易被低估。实际上,如果只是将一个 POGO 映射到一个表,那么工作相当简单 —POGO 的属性恰好映射到表列。但是当对象模型稍稍变复杂一点,例如有两个彼此相关的 POGO,那么事情将很快变得困难起来。

  例如,请看上个月 文章 中开始的旅行规划网站。显然,Trip POGO 在应用程序中有重要的作用。请在文本编辑器中打开 grails-app/domain/Trip.groovy(如清单 1 所示):

  清单 1. Trip 类

class Trip {
  
String name
  
String city
  
Date startDate
  
Date endDate
  
String purpose
  
String notes
}

  清单 1 中的每个属性都轻松漂亮地映射到 Trip 表中的对应字段。还记得在上一期的文章中说过,在 Grail 启动时,所有存储在 grails-app/domain 目录下的 POGO 都会自动创建对应的表。默认情况下,Grails 使用内嵌的 HSQLDB 数据库,但是到本文结束时,就能够使用自己喜欢的其他任意关系数据库。

  旅程中经常要包含飞行,所以还应该创建一个 Airline 类(如清单 2 所示):

  清单 2. Airline 类

class Airline {
  
String name
  
String url
  
String frequentFlyer
  
String notes
}

  现在要将这两个类链接起来。为了计划一个通过 Xyz 航线到芝加哥的旅行,在 Groovy 代码中的表示方法与在 Java 代码中的表示方法相同 — 要在 Trip 类中添加一个 Airline 属性(如清单 3 所示)。这个技术称为对象组合(object composition)(请参阅 参考资料)。

  清单 3.在 Trip 类中添加 Airline 属性

class Trip {
  
String name
  
String city
  ...
  Airline airline
}

  对于软件模型来说,这种表示方法非常合适,但是关系数据库采取的表示方法略有不同。表中的每个记录都有一个惟一的 ID,称为主键。向 Trip 表添加一个 airline_id 字段,就能将一个记录与另一个记录链接在一起(在这个示例中,“Xyz航线” 记录与 “芝加哥旅行” 记录链接)。这称为一对多 关系:一个航线能够与多个旅行关联。(在 Grails 的联机文档中,可以找到一对一和多对多关系的示例,请参阅 参考资料。)

  这样形成的数据库模式只有一个问题。您可能对数据库成功地进行了规范化(请参阅 参考资料),但是现在表中的列与软件模型就失去了同步。如果将 Airline 字段替换成 AirlineId 字段,那么实现的细节(在数据库中持久化 POGO)就泄漏 到了对象模型。Joel Spolsky 将这种情况称为 抽象泄漏法则(Law of Leaky Abstractions)(请参阅 参考资料)。

  GORM 有助于缓解抽象泄漏问题,它支持使用对 Groovy 有意义的方式表示对象模型,由 GORM 在幕后处理关系数据库的问题。但是正如即将看到的,如果需要,覆盖默认设置也很容易。GORM 并不是隐藏数据库细节的不透明的 抽象层,而是一个半透明的 层 — 它尝试在不进行用户干预的情况下执行正确的工作,但是如果用户需要对它的行为进行自定义,它也可以提供支持。这样它就提供了两方面的好处。

  现在已经在 POGO 类 Trip 中添加了 Airline 属性。要完成一对多关系,还要在 Trip 这个 POGO 中添加一个 hasMany 设置,如清单 4 所示:

  清单 4. 在 Airline 中建立一对多关系

class Airline {
  static hasMany
= [trip:Trip]

  
String name
  
String url
  
String frequentFlyer
  
String notes
}

  静态的 hasMany 设置是个 Groovy 的 hashmap:键是 trip;值是 Trip 类。如果要在 Airline 类中设置额外的一对多关系,那么可以将逗号分隔的键/值对放在方括号内。

  现在在 grails-app/controllers 中迅速创建一个 AirlineController 类(如清单 5 所示),这样就能看出新的一对多关系的效果:

  清单 5. AirlineController class

class AirlineController {
  def scaffold
= Airline
}    

  还记得在上一期的文章中说过 def scaffold 的功能是告诉 Grails 在运行的时候动态创建基本的 list()、save() 和 edit() 方法。它还告诉 Grails 动态创建 GroovyServer Page(GSP)视图。请确保 TripController 和 AirlineController 都包含 def scaffold。如果曾经因为输入 grails generate-all 在 grails-app/views 中生成过任何 GSP 工件,例如 trip 目录或者是 airline 目录,都应该删除它们。对于这个示例,需要确保既允许 Grails 动态搭建控制器,又允许它动态搭建视图。

  现在域类和控制器类都已经就位,请启动 Grails。请输入 grails prod run-app 在生产模式下运行应用程序。如果一切正常,应该看到欢迎消息:

  Server running. Browse to http://localhost:8080/trip-planner

  在浏览器中,应该看到 AirlineController 和 TripController 链接。单击 AirlineController 链接,填写 Xyz 航线的详细信息,如图 1 所示:

  图 1. 一对多关系:一方  

  如果不喜欢字段按照字母顺序排序,也不用担心。在下一节就能改变这种方式。

  现在新建一个旅程,如图 2 所示。请注意 Airline 的组合框。添加到 Airline 表的每个记录都在这里显示。不用担心 “泄漏” 主键 — 在下一节将会看到如何添加更具描述性的标签。

  图 2. 一对多关系:多方  

0
相关文章