精通 Grails: Grails 与遗留数据库

约定优于配置能够应用到非传统的数据库模式吗?

在本期的 精通 Grails中,Scott Davis 探讨了 Grails 能够使用不符合 Grails 命名标准的数据库表的多种方法。如果您的 Java™类已经映射到了遗留的数据库中,Grails 允许您直接使用它们,无需做任何改变。您将看到将 Hibernate HBM 文件以及 Enterprise JavaBeans 3 注释与遗留 Java 类结合使用的例子。

Scott Davis , 主编, AboutGroovy.com

Scott DavisScott Davis 是国际知名作家、演讲家和软件开发人员。他出版的书籍包括 Groovy Recipes: Greasing the Wheels of JavaGIS for Web Developers: Adding Where to Your ApplicationThe Google Maps APIJBoss At Work



2008 年 8 月 04 日

Grails 对象关系映射(Grails Object Relational Mapping,GORM)API 是 Grails Web 框架的核心部分之一。“精通 Grails:GORM - 有趣的名称,严肃的技术” 向您介绍了 GORM 的基础知识,包括简单的一对多关系。之后的 “使用 Ajax 实现多对多关系” 教您使用 GORM 建模越来越复杂的类关系。现在您将看到 GORM 的 “ORM” 如何能够灵活处理遗留数据库中不符合 Grails 标准命名约定的表名与列名。

备份并恢复数据

无论什么时候处理数据库中的现有数据,都要有一份最新的备份。著名的墨菲法则(Murphy's Law )的墨菲(Murphy)就像是我的守护神。什么样的错误都有可能发生,所以还是未雨绸缪的好。

关于本系列

Grails 是一种新型 Web 开发框架,它将常见的 Spring 和 Hibernate 等 Java 技术与当前流行的约定优于配置等实践相结合。Grails 是用 Groovy 编写的,它可以提供与遗留 Java 代码的无缝集成,同时还可以加入脚本编制语言的灵活性和动态性。学习完 Grails 之后,您将彻底改变看待 Web 开发的方式。

备份

除了用常规备份软件备份目标数据库外,我还建议再保存一份数据的纯文本副本。这样就能够用相同的数据集轻松地创建测试和开发数据库了,还可以轻松地跨数据库服务器移动数据(例如,在 MySQL 和 DB2 之间来回移动数据)。

您将再一次使用本系列一直开发的 Trip Planner 应用程序。清单 1 是一个名为 backupAirports.groovy 的 Groovy 脚本,它备份了 airport表的记录。它用了三条语句、不足 20 行的代码连接到了数据库,从表中选定了每一行,并将数据作为 XML 导出。

清单 1. backupAirports.groovy
 sql = groovy.sql.Sql.newInstance( 
   "jdbc:mysql://localhost/trip?autoReconnect=true", 
   "grails", 
   "server", 
   "com.mysql.jdbc.Driver") 

 x = new groovy.xml.MarkupBuilder() 

 x.airports{ 
  sql.eachRow("select * from airport order by id"){ row -> 
    airport(id:row.id){ 
      version(row.version) 
      name(row.name) 
      city(row.city) 
      state(row.state) 
      country(row.country) 
      iata(row.iata) 
      lat(row.lat) 
      lng(row.lng) 
    } 
  } 
 }

清单 1 中的第一条语句创建了一个新的 groovy.sql.Sql对象。这是一个标准 JDBC 类集的瘦 Groovy facade,包括 ConnectionStatementResultSet。您可能已经认出了 newInstance工厂方法的四个参数了:JDBC 连接字符串、用户名、密码以及 JDBC 驱动程序(在 grails-app/conf/DataSource.groovy  中也可以找到相同值)。

下一条语句创建了 groovy.xml.MarkupBuilder。该类允许您动态创建 XML 文档。

最后一条语句(以 x.airports开头)创建了 XML 树。XML 文档的根元素为 airports。它还为数据库的每一行创建了一个 airport元素,该元素带有 id属性。嵌套于 airport元素的元素有 versionnamecity元素(想了解更多关于 Groovy SqlMarkupBuilder用途的信息,参见 参考资料)。

清单 2 展示了由此得到的 XML:

清单 2. 来自备份脚本的 XML 输出
 <airports> 
  <airport id='1'> 
    <version>2</version> 
    <name>Denver International Airport</name> 
    <city>Denver</city> 
    <state>CO</state> 
    <country>US</country> 
    <iata>den</iata> 
    <lat>39.8583188</lat> 
    <lng>-104.6674674</lng> 
  </airport> 
  <airport id='2'>...</airport> 
  <airport id='3'>...</airport> 
 </airports>

在备份脚本中,一定要按照主键顺序拖出记录。当恢复这个数据时,一定要按相同的顺序插入值,以确保外键值同样匹配(关于这一点我将在下一小节进一步详述)。

注意,该脚本是完全独立于 Grails 框架的。要使用它,就一定要在您的系统上安装 Groovy(参见 参考资料,查找下载与安装说明)。另外,类路径中一定要有 JDBC 驱动程序 JAR。可以在运行脚本时进行指定。在 UNIX®中,要输入:

 groovy -classpath /path/to/mysql.jar:. backupAirports.groovy

当然了,在 Windows®上,相应的文件路径和 JAR 分隔符是不同的。在 Windows 中,则需要输入:

 groovy -classpath c:\path\to\mysql.jar;. backupAirports.groovy

由于我经常使用 MySQL,所以我将一份该 JAR 的副本保存在了我的主目录(在 UNIX 上为 /Users/sdavis,在 Windows 上为 c:\Documents and Settings\sdavis)中的 .groovy/lib 目录中。当从命令行运行 Groovy 脚本时,该目录中的 JAR 会自动包含在类路径中。

清单 1 中的脚本将输出写到了屏幕。要将数据保存在一个文件中,可以在运行脚本时重定向输出:

groovy backupAirports.groovy > airports.xml

恢复数据

从数据库中获取出数据仅仅是成功了一半。还要再将数据恢复到数据库中。清单 3 中展示的 restoreAirports.groovy 脚本用 Groovy XmlParser读入了 XML,构造了一个 SQL insert语句,并用了一个 Groovy SQL对象来执行该语句(要了解更多关于 XmlParser的信息,参见 参考资料)。

清单 3. 从 XML 中恢复数据库记录的 Groovy 脚本
 if(args.size()){ 
   f = new File(args[0]) 
   println f 

   sql = groovy.sql.Sql.newInstance( 
      "jdbc:mysql://localhost/aboutgroovy?autoReconnect=true", 
      "grails", 
      "server", 
      "com.mysql.jdbc.Driver") 

   items = new groovy.util.XmlParser().parse(f) 
   items.item.each{item -> 
     println "${item.@id} -- ${item.title.text()}"
      sql.execute( 
         "insert into item (version, title, short_description, description, 
                 url, type, date_posted, posted_by) values(?,?,?,?,?,?,?,?)", 
         [0, item.title.text(), item.shortDescription.text(), item.description.text(), 
             item.url.text(), item.type.text(), item.datePosted.text(), 
             item.postedBy.text()] 
         ) 
   } 
 } 
 else{ 
   println "USAGE: itemsRestore [filename]"
 }

要运行该脚本,需要输入:

groovy restoreAirports.groovy airports.xml

切记,对于要工作的表之间的关系而言,关系的 的方面的主键字段一定要与关系的 的方面的外键字段相匹配。例如,储存于 airport表的 id列中的值一定要与 flight表的 arrival_airline_id列中的值相同。

转换 USGS 数据

USGS 将机场的数据提供为一个 shapefile。这是一个交换地理数据的固定文件格式。一个 shapefile 至少由三个文件组成。.shp 文件包含地理数据 —此处指每个机场的纬度 / 经度。.shx 文件是一个空间索引。.dbf 文件是 —您猜对了 —一个很好的古老的 dBase 文件,它包含了所有的非空间数据(此处指机场名、IATA 代码等)。

我使用了 Geospatial Data Abstraction Library(GDAL)—一套处理地理数据的开源的命令行工具集 —来将 shapefile 转换为文中使用的地理标记语言(Geography Markup Language,GML)文件。我使用的具体的命令为:

ogr2ogr -f "GML" airports.xml airprtx020.shp

您还可以使用 GDAL 将数据转换为逗号分割值(comma-separated value,CSV)、GeoJSON、Keyhole 标记语言(Keyhole Markup Language,KML)以及其他很多种格式。

为了确保自动编号的 id字段被恢复为相同的值,一定要在恢复表前将它们全部删除。这样就可以在下次启动 Grails 重新创建表时将自动编号重置为 0。

将机场数据安全地备份之后(大概其他表中的数据也已经安全备份了),那么现在您就可以开始试验一些新的 “遗留” 数据了。不懂么?看完下一小节您就会明白了。

导入新的机场数据

美国地质勘探局(United States Geological Survey,USGS)发表了一个全面的美国机场的列表,包括 IATA 代码和纬度 / 经度(参见 参考资料)。果然,USGS 字段与现行使用的 Airport类不相匹配。虽然可以改变 Grails 类,使它与 USGS 表中的名称相匹配,但是这要大量改写应用程序。相反,本文不需要这样做,而是探讨几种不同的技术,在后台将现有的 Airport类无缝映射到新的、不同的表模式中。

首先,需要将 USGS “遗留” 数据导入到数据库。然后运行清单 4 中的 createUsgsAirports.groovy 脚本,创建新表(该脚本假设您正在使用 MySQL。由于每个数据库创建新表的语法有所不同,所以使用其他数据库时,需要对该脚本做出适当修改)。

清单 4. 创建 USGS 机场表
 sql = groovy.sql.Sql.newInstance( 
   "jdbc:mysql://localhost/trip?autoReconnect=true", 
   "grails", 
   "server", 
   "com.mysql.jdbc.Driver") 

 ddl = """
 CREATE TABLE usgs_airports ( 
  airport_id bigint(20) not null, 
  locid varchar(4), 
  feature varchar(80), 
  airport_name varchar(80), 
  state varchar(2), 
  county varchar(50), 
  latitude varchar(30), 
  longitude varchar(30), 
  primary key(airport_id) 
 ); 
"""

 sql.execute(ddl)

看一看清单 5 中的 usgs-airports.xml。它是 GML 格式的一个例子。该 XML 要比清单 2 中由备份脚本创建的简单的 XML 复杂一些。这其中,每一个元素都处在一个名称空间中,而且元素嵌套得更深。

清单 5. GML 格式的 USGS 机场表
 <?xml version="1.0" encoding="utf-8" ?> 
 <ogr:FeatureCollection 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://ogr.maptools.org/airports.xsd"
     xmlns:ogr="http://ogr.maptools.org/"
     xmlns:gml="http://www.opengis.net/gml"> 
                         
  <gml:featureMember> 
    <ogr:airprtx020 fid="F0"> 
      <ogr:geometryProperty> 
        <gml:Point> 
          <gml:coordinates>-156.042831420898438,19.73573112487793</gml:coordinates> 
        </gml:Point> 
      </ogr:geometryProperty> 
      <ogr:AREA>0.000</ogr:AREA> 
      <ogr:PERIMETER>0.000</ogr:PERIMETER> 
      <ogr:AIRPRTX020>1</ogr:AIRPRTX020> 
      <ogr:LOCID>KOA</ogr:LOCID> 
      <ogr:FEATURE>Airport</ogr:FEATURE> 
      <ogr:NAME>Kona International At Keahole</ogr:NAME> 
      <ogr:TOT_ENP>1271744</ogr:TOT_ENP> 
      <ogr:STATE>HI</ogr:STATE> 
      <ogr:COUNTY>Hawaii County</ogr:COUNTY> 
      <ogr:FIPS>15001</ogr:FIPS> 
      <ogr:STATE_FIPS>15</ogr:STATE_FIPS> 
    </ogr:airprtx020> 
  </gml:featureMember> 

  <gml:featureMember>...</gml:featureMember> 
  <gml:featureMember>...</gml:featureMember> 
 </ogr:FeatureCollection>

现在,创建如清单 6 所示的 restoreUsgsAirports.groovy 脚本。要获取具有名称空间的元素,需要声明几个 groovy.xml.Namespace变量。与前面的 restoreAirport.groovy 脚本(清单 3)中使用的简单的点表示法不同,这里的具有名称空间的元素要用方括号括上。

清单 6. 将 USGS 机场数据恢复到数据库
 if(args.size()){ 
  f = new File(args[0]) 
  println f 

  sql = groovy.sql.Sql.newInstance( 
     "jdbc:mysql://localhost/trip?autoReconnect=true", 
     "grails", 
     "server", 
     "com.mysql.jdbc.Driver") 

  FeatureCollection = new groovy.util.XmlParser().parse(f) 
  ogr = new groovy.xml.Namespace("http://ogr.maptools.org/") 
  gml = new groovy.xml.Namespace("http://www.opengis.net/gml") 
  
  FeatureCollection[gml.featureMember][ogr.airprtx020].each{airprtx020 -> 
   println "${airprtx020[ogr.LOCID].text()} -- ${airprtx020[ogr.NAME].text()}"
   points = airprtx020[ogr.geometryProperty][gml.Point][gml.coordinates].text().split(",") 
    
     sql.execute( 
        "insert into usgs_airports (airport_id, locid, feature, airport_name, state, 
        county, latitude, longitude) values(?,?,?,?,?,?,?,?)", 
        [airprtx020[ogr.AIRPRTX020].text(), 
        airprtx020[ogr.LOCID].text(), 
        airprtx020[ogr.FEATURE].text(), 
        airprtx020[ogr.NAME].text(), 
        airprtx020[ogr.STATE].text(), 
        airprtx020[ogr.COUNTY].text(), 
        points[1], 
        points[0]] 
        )    
  } 
 } 
 else{ 
   println "USAGE: restoreAirports [filename]"
 }

在命令指示符处输入如下信息,将 usgs_airports.xml 文件中的数据插入到 新创建的表中:

groovy restoreUsgsAirports.groovy 
 usgs-airports.xml

验证数据插入成功:从命令行登入 MySQL,确保数据已经就位,如清单 7 所示:

清单 7. 验证数据库中的 USGS 机场数据
 $ mysql --user=grails -p --database=trip 

 mysql> desc usgs_airports; 
 +--------------+-------------+------+-----+---------+-------+ 
 | Field        | Type        | Null | Key | Default | Extra | 
 +--------------+-------------+------+-----+---------+-------+ 
 | airport_id   | bigint(20)  | NO   | PRI |         |       | 
 | locid        | varchar(4)  | YES  |     | NULL    |       | 
 | feature      | varchar(80) | YES  |     | NULL    |       | 
 | airport_name | varchar(80) | YES  |     | NULL    |       | 
 | state        | varchar(2)  | YES  |     | NULL    |       | 
 | county       | varchar(50) | YES  |     | NULL    |       | 
 | latitude     | varchar(30) | YES  |     | NULL    |       | 
 | longitude    | varchar(30) | YES  |     | NULL    |       | 
 +--------------+-------------+------+-----+---------+-------+ 
 8 rows in set (0.01 sec) 

 mysql> select count(*) from usgs_airports; 
 +----------+ 
 | count(*) | 
 +----------+ 
 |      901 | 
 +----------+ 
 1 row in set (0.44 sec) 

 mysql> select * from usgs_airports limit 1\G 
 *************************** 1. row *************************** 
  airport_id: 1 
       locid: KOA 
     feature: Airport 
 airport_name: Kona International At Keahole 
       state: HI 
      county: Hawaii County 
    latitude: 19.73573112487793 
   longitude: -156.042831420898438

禁用 dbCreate

遗留表就位之后,您需要做最后一件事:禁用 grails-app/conf/DataSource.groovy 中的 dbCreate变量。回想一下 “GORM:有趣的名称,严肃的技术” 就会知道,如果相应的表不存在的话,该变量会指示 GORM 在后台创建它,并且会改变任何现有表,从而匹配 Grails 域类。因此,如果要处理遗留表的话,就一定要禁用该特性,这样 GORM 才不会破坏其他应用程序可能会用到的模式。

如果能够有选择地为特定的表启用或禁用 dbCreate就好了。不幸的是,它是一个全局的 “全有或全无” 的设置。我遇到既有新表又有遗留表的情况时,会先允许 GORM 创建新表,然后禁用 dbCreate,导入现有的遗留表。在这样的情况下,您就会了解到有一个好的备份与恢复策略是多么重要了。


静态映射块

我将示范的第一个将域类映射到遗留表的策略是 使用静态 mapping块。大多数情况下我都会使用这个块,因为它感觉最像 Grails。我习惯将静态 constraints块添加到域类,这样添加静态 mapping块感觉起来和添加框架的其余部分是一致的。

将 grails-app/domain/Airport.groovy 文件复制到 grails-app/domain/AirportMapping.groovy。这个名称只是为了示范用的。因为将会有三个类全部映射回相同的表中,因此需要有一种方式来将每一个类单独命名(这在真实的应用程序中不大可能会发生)。

注释掉城市与国家字段,因为在新的表中没有这些字段。然后从 constraints块中移除这些字段。现在添加 mapping块,将 Grails 的名称链接到数据库名,如清单 8 所示:

清单 8. AirportMapping.groovy
 class AirportMapping{ 
  static constraints = { 
    name() 
    iata(maxSize:3) 
    state(maxSize:2) 
    lat() 
    lng() 
  } 
  
  static mapping = { 
    table "usgs_airports"
    version false 
    columns { 
      id column: "airport_id"
      name column: "airport_name"
      iata column: "locid"
      state column: "state"
      lat column: "latitude"
      lng column: "longitude"              
    } 
  } 
  
  String name 
  String iata 
  //String city 
  String state 
  //String country = "US"
  String lat 
  String lng 
  
  String toString(){ 
    "${iata} - ${name}"
  } 
 }

mapping块的第一条语句将 AirportMapping类链接到 usgs_airports表。下一条语句通知 Grails 表没有 version列(GORM 通常会创建一个 version列来优化乐观锁定)。最后,columns块将 Grails 名称映射到数据库名称。

注意,使用了这个映射技术,表中的某些特定的字段是可以忽略的。 在这种情况下,featurecounty 列未表示在域类中。要想让未储存于表中的字段存在于域类中,可以添加静态 transients行。该行看起来与一对多关系中使用的 belongsTo变量类似。例如,如果 Airport类中有两个字段不需要储存到表中,代码会是这样的:

static transients = ["tempField1", "tempField2"]

此处示范的 mapping块仅仅涉及到此技术可以实现的皮毛而已。想了解更多的信息,参见 参考资料

设置遗留表为只读

输入 grails generate-all AirportMapping,创建 控制器和 GSP 视图。由于此表实质上是一个查找表,所以请进入 grails-app/controllers/AirportMappingController.groovy,只留下 listshow闭包。移除 deleteeditupdatecreate以及 save(不要忘记从 allowedMethods变量中移除 deleteeditsave。可以完全移除整个行,或者只留下方括号空集)。

要使该视图为只读,还需要做几个快捷的更改。首先,从 grails-app/views/airportMapping/list.gsp 顶端移除 New AirportMapping链接。然后对 grails-app/views/airportMapping/show.gsp 做相同操作。最后,从 show.gsp 底部移除 editdelete按钮。

输入 grails run-app,验证 mapping块可以运行。请看一下图 1 中展示的页面:

图 1. 验证 mapping块可以运行
验证 mapping 块可以运行

结合使用遗留 Java 类与 Hibernate 映射文件

了解了 mapping块后,让我们再深入一步。不难想象如果拥有了遗留表,就有可能也拥有了遗留 Java 类。如果您想将现有 Java 代码与现有表中的数据融合,可以使用接下来的两个映射技术。

在 Java 1.5 引入注释之前,Hibernate 用户需要创建名为 HBM 文件的 XML 映射文件。回忆一下,GORM 是一个优于 Hibernate 的瘦 Groovy facade,因此,那些古老的 Hibernate 技巧仍然奏效也不足为奇。

首先,将遗留 Java 源文件复制到 src/java。如果使用包的话,要为每一个包名创建一个目录。例如,清单 9 中所示的 AirportHbm.java 文件位于 org.davisworld.trip包中。这意味着该文件的完整路径应该是 src/java/org/davisworld/trip/AirportHbm.java。

清单 9. AirportHbm.java
 package org.davisworld.trip; 

 public class AirportHbm { 
  private long id; 
  private String name; 
  private String iata; 
  private String state; 
  private String lat; 
  private String lng; 

  public long getId() { 
    return id; 
  } 

  public void setId(long id) { 
    this.id = id; 
  } 
  
  // all of the other getters/setters go here 
 }

Java 文件一旦就位,就可以挨着它创建一个清单 10 中所示的名为 AirportHbmConstraints.groovy 的 “影子” 文件了。该文件中可以放置本应该位于域类中的静态 constraints块。切记该文件一定要与 Java 类位于相同的包中。

清单 10. AirportHbmConstraints.groovy
 package org.davisworld.trip 

 static constraints = { 
  name() 
  iata(maxSize:3) 
  state(maxSize:2) 
  lat() 
  lng() 
 }

src 目录下的文件会在运行应用程序或者创建要部署的 WAR 文件时编译。如果已经编译了 Java 代码的话,也可以仅将它压缩为 JAR 文件并将其置于 lib 目录中。

接下来,让我们来建立控制器。按照约定优于配置的规定,控制器应该命名为 AirportHbmController.groovy。由于 Java 类位于一个包中,因此可以将控制器置于同一包中,或是在文件顶部导入 Java 类。我更偏爱导入的方法,如清单 11 所示:

清单 11. AirportHbmController.groovy
 import org.davisworld.trip.AirportHbm 

 class AirportHbmController { 
  def scaffold = AirportHbm 
 }

接下来,将现有的 HBM 文件复制到 grails-app/conf/hibernate。应该会有一个如清单 12 所示的单一的 hibernate.cfg.xml 文件,您要在这里指定每一个类用的映射文件。在本例中,应该会有一个 AirportHbm.hbm.xml 文件的条目。

清单 12. 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 resource="AirportHbm.hbm.xml"/> 
    </session-factory> 
 </hibernate-configuration>

每一个类都必须有它自己的 HBM 文件。该文件为先前使用的静态 mapping块的 XML 等价体。清单 13 展示了 AirportHbm.hbm.xml:

清单 13. AirportHbm.hbm.xml
 <?xml version="1.0"?> 
 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 
 <hibernate-mapping> 
    <class name="org.davisworld.trip.AirportHbm" table="usgs_airports"> 
        <id name="id" column="airport_id"> 
            <generator class="native"/> 
         </id>          
        <property name="name" type="java.lang.String"> 
            <column name="airport_name" not-null="true" /> 
        </property> 
        <property name="iata" type="java.lang.String"> 
            <column name="locid" not-null="true" /> 
        </property> 
        <property name="state" /> 
        <property name="lat" column="latitude" /> 
        <property name="lng" column="longitude" /> 
    </class> 
 </hibernate-mapping>

包的完整名是参考 Java 类而指定的。剩余的条目将 Java 名映射到表名。nameiata字段条目演示了长表单。由于 state字段在 Java 代码中和表中是一样的,因此可以将其条目缩短。最后两个字段 —latlng—演示了缩短了的语法(想了解更多关于 Hibernate 映射文件的信息,参见 参考资料)。

如果 Grails 仍在运行的话,重新启动它。现在应该能够在 http://localhost:8080/trip/airportHbm 看到 Hibernate 映射数据。


对 Java 类使用 Enterprise JavaBeans(EJB)3 注释

正如我在上面所提到的,Java 1.5 引入了注释。注释允许您通过添加 @前缀的方式直接向 Java 类添加元数据。Groovy 1.0 在其发行初期(2006 年 12 月)并不支持 Java 1.5 的诸如注释这样的语言特性。一年以后发行的 Groovy 1.5 则发生了翻天覆地的变化。这就意味着您也可以将 EJB3 注释的 Java 文件引入到一个现有的 Grails 应用程序中了。

再次启动 EJB3 注释的 Java 文件。将清单 14 展示的 AirportAnnotation.java 置于 src/java/org.davisworld.trip 中,紧挨着 AirportHbm.java 文件:

清单 14. AirportAnnotation.java
 package org.davisworld.trip; 

 import javax.persistence.*; 

 @Entity 
 @Table(name="usgs_airports") 
 public class AirportAnnotation { 
  private long id; 
  private String name; 
  private String iata; 
  private String state; 
  private String lat; 
  private String lng; 

  @Id 
  @Column(name="airport_id", nullable=false) 
  public long getId() { 
    return id; 
  } 

  @Column(name="airport_name", nullable=false) 
  public String getName() { 
    return name; 
  } 

  @Column(name="locid", nullable=false) 
  public String getIata() { 
    return iata; 
  } 

  @Column(name="state", nullable=false) 
  public String getState() { 
    return state; 
  } 

  @Column(name="latitude", nullable=false) 
  public String getLat() { 
    return lat; 
  } 

  @Column(name="longitude", nullable=false) 
  public String getLng() { 
    return lng; 
  } 

  // The setter methods don't have an annotation on them. 
  // They are not shown here, but they should be in the file 
  //   if you want to be able to change the values. 

 }

注意,一定要导入文件顶部的 javax.persistence包。@Entity@Table注释了类声明,将它映射到了适当的数据库表中。其他的注释位于每一个字段的 getter 方法之上。所有的字段都应该有 @Column注释,它将字段名映射到列名。主键也应该有一个 @ID注释。

清单 15 中的 AirportAnnotationConstraints.groovy 文件与前面清单 10 中的例子没什么不同:

清单 15. AirportAnnotationConstraints.groovy
 package org.davisworld.trip 

 static constraints = { 
  name() 
  iata(maxSize:3) 
  state(maxSize:2) 
  lat() 
  lng() 
 }

AirportAnnotationController.groovy(清单 16 中所展示的)是按照通常的方式搭建的:

清单 16. AirportAnnotationController.groovy
 import org.davisworld.trip.AirportAnnotation 

 class AirportAnnotationController { 
  def scaffold = AirportAnnotation 
 }

hibernate.cfg.xml 文件再次开始其作用。这回,语法有点不同。您直接将它指向类,而不是指向一个 HBM 文件,如清单 17 所示:

清单 17. 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 resource="AirportHbm.hbm.xml"/> 
        <mapping class="org.davisworld.trip.AirportAnnotation"/>         
    </session-factory> 
 </hibernate-configuration>

要让注释开始生效,还需要做最后一件事。Grails 并不是本来就被设置成可以查找 EJB3 注释的。而是需要导入 grails-app/conf/DataSource.groovy 中的一个特定的类,如清单 18 所示:

清单 18. DataSource.groovy
 import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration 
 dataSource { 
  configClass = GrailsAnnotationConfiguration.class 
  
   pooled = false 
   driverClassName = "com.mysql.jdbc.Driver"
   username = "grails"
   password = "server"
 }

一旦导入了 org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration并允许 Spring 将其作为 configClass而注入 dataSource块之后,Grails 就会支持 EJB3 注释了,同时它还可以支持 HBM 文件和本地映射块。

如果忘了这最后一步的话(我几乎每一次在 Grails 中使用 EJB3 注释时都会忘记这一步 ),会得到如下的错误信息:

清单 19. 未注入 DataSource.groovy 中的 configClass时抛出的异常
 org.hibernate.MappingException: 
 An AnnotationConfiguration instance is required to use 
 <mapping class="org.davisworld.trip.AirportAnnotation"/>

结束语

这样看来,将 对象映射Grails关系数据库中应该易如反掌(毕竟,这正是它被命名为 GORM的原因)。一旦您有信心能够轻松备份和恢复数据,您就会有很多种方式使 Grails 符合遗留数据库中的非标准的命名约定。静态的 mapping块是完成这个任务的最简单的方式,因为它与 Grails 最相似。但如果您的遗留 Java 类已经映射到了遗留数据库中的话,那就不用多此一举了。无论您使用 HBM 文件还是较新的 EJB3 注释,Grails 都可以直接利用您已经完成的成果,这样您就可以投身其他的任务了。

在下一篇文章中,您将有机会了解 Grails 事件模型。从构建脚本到单个的 Grails 工件(域类、控制器等),所有这些都会在应用程序的生命周期的关键点抛出事件。因此在下一篇文章中,您将学习如何设置侦听器来捕获这些事件,并使用定制操作回应。到那时,请尽情享受精通 Grails 带来的乐趣吧。


下载

描述名字大小
样例代码1j-grails07158.zip991KB

注意:

  1. 下载的代码中含有 Grails 应用程序、Groovy 脚本以及 GML 格式的 USGS Airport 文件。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Web development, Open source
ArticleID=326830
ArticleTitle=精通 Grails: Grails 与遗留数据库
publish-date=08042008