精通 Grails: Grails 与遗留数据库,约定优于配置能够应用到非传统的数据库模式吗?

5 篇文章 0 订阅

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

备份并恢复数据

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

备份

除了用常规备份软件备份目标数据库外,我还建议再保存一份数据的纯文本副本。这样就能够用相同的数据集轻松地创建测试和开发数据库了,还可以轻松地跨数据库服务器移动数据(例如,在 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 机场表

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <ogr:FeatureCollection
  3.      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.      xsi:schemaLocation="http://ogr.maptools.org/airports.xsd"
  5.      xmlns:ogr="http://ogr.maptools.org/"
  6.      xmlns:gml="http://www.opengis.net/gml">
  7.                          
  8.   <gml:featureMember>
  9.     <ogr:airprtx020 fid="F0">
  10.       <ogr:geometryProperty>
  11.         <gml:Point>
  12.           <gml:coordinates>-156.042831420898438,19.73573112487793</gml:coordinates>
  13.         </gml:Point>
  14.       </ogr:geometryProperty>
  15.       <ogr:AREA>0.000</ogr:AREA>
  16.       <ogr:PERIMETER>0.000</ogr:PERIMETER>
  17.       <ogr:AIRPRTX020>1</ogr:AIRPRTX020>
  18.       <ogr:LOCID>KOA</ogr:LOCID>
  19.       <ogr:FEATURE>Airport</ogr:FEATURE>
  20.       <ogr:NAME>Kona International At Keahole</ogr:NAME>
  21.       <ogr:TOT_ENP>1271744</ogr:TOT_ENP>
  22.       <ogr:STATE>HI</ogr:STATE>
  23.       <ogr:COUNTY>Hawaii County</ogr:COUNTY>
  24.       <ogr:FIPS>15001</ogr:FIPS>
  25.       <ogr:STATE_FIPS>15</ogr:STATE_FIPS>
  26.     </ogr:airprtx020>
  27.   </gml:featureMember>
  28.   <gml:featureMember>...</gml:featureMember>
  29.   <gml:featureMember>...</gml:featureMember>
  30. </ogr:FeatureCollection>  

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


清单 6. 将 USGS 机场数据恢复到数据库

 

  1. if(args.size()){
  2.   f = new File(args[0])
  3.   println f
  4.   sql = groovy.sql.Sql.newInstance(
  5.      "jdbc:mysql://localhost/trip?autoReconnect=true",
  6.      "grails",
  7.      "server",
  8.      "com.mysql.jdbc.Driver")
  9.   FeatureCollection = new groovy.util.XmlParser().parse(f)
  10.   ogr = new groovy.xml.Namespace("http://ogr.maptools.org/")
  11.   gml = new groovy.xml.Namespace("http://www.opengis.net/gml")
  12.   
  13.   FeatureCollection[gml.featureMember][ogr.airprtx020].each{airprtx020 ->
  14.    println "${airprtx020[ogr.LOCID].text()} -- ${airprtx020[ogr.NAME].text()}"
  15.    points = airprtx020[ogr.geometryProperty][gml.Point][gml.coordinates].text().split(",")
  16.     
  17.      sql.execute(
  18.         "insert into usgs_airports (airport_id, locid, feature, airport_name, state, 
  19.         county, latitude, longitude) values(?,?,?,?,?,?,?,?)",
  20.         [airprtx020[ogr.AIRPRTX020].text(), 
  21.         airprtx020[ogr.LOCID].text(), 
  22.         airprtx020[ogr.FEATURE].text(), 
  23.         airprtx020[ogr.NAME].text(), 
  24.         airprtx020[ogr.STATE].text(), 
  25.         airprtx020[ogr.COUNTY].text(), 
  26.         points[1], 
  27.         points[0]]
  28.         )    
  29.   }
  30. }
  31. else{
  32.    println "USAGE: restoreAirports [filename]"
  33. }

在命令指示符处输入如下信息,将 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

  1. class AirportMapping{
  2.   static constraints = {
  3.     name()
  4.     iata(maxSize:3)
  5.     state(maxSize:2)
  6.     lat()
  7.     lng()
  8.   }
  9.   
  10.   static mapping = {
  11.     table "usgs_airports"
  12.     version false
  13.     columns {
  14.       id column: "airport_id"
  15.       name column: "airport_name"
  16.       iata column: "locid"
  17.       state column: "state"
  18.       lat column: "latitude"
  19.       lng column: "longitude"              
  20.     }
  21.   }
  22.   
  23.   String name
  24.   String iata
  25.   //String city
  26.   String state
  27.   //String country = "US"
  28.   String lat
  29.   String lng
  30.   
  31.   String toString(){
  32.     "${iata} - ${name}"
  33.   }
  34. }

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

  1. package org.davisworld.trip;
  2. public class AirportHbm {
  3.   private long id;
  4.   private String name;
  5.   private String iata;
  6.   private String state;
  7.   private String lat;
  8.   private String lng;
  9.   public long getId() {
  10.     return id;
  11.   }
  12.   public void setId(long id) {
  13.     this.id = id;
  14.   }
  15.   
  16.   // all of the other getters/setters go here
  17. }

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

 

  1. <?xml version="1.0"?>
  2. <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  3. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  4. <hibernate-mapping>
  5.     <class name="org.davisworld.trip.AirportHbm" table="usgs_airports">
  6.         <id name="id" column="airport_id">
  7.             <generator class="native"/>
  8.          </id>          
  9.         <property name="name" type="java.lang.String">
  10.             <column name="airport_name" not-null="true" />
  11.         </property>
  12.         <property name="iata" type="java.lang.String">
  13.             <column name="locid" not-null="true" />
  14.         </property>
  15.         <property name="state" />
  16.         <property name="lat" column="latitude" />
  17.         <property name="lng" column="longitude" />
  18.     </class>
  19. </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

  1. package org.davisworld.trip;
  2. import javax.persistence.*;
  3. @Entity
  4. @Table(name="usgs_airports")
  5. public class AirportAnnotation {
  6.   private long id;
  7.   private String name;
  8.   private String iata;
  9.   private String state;
  10.   private String lat;
  11.   private String lng;
  12.   @Id 
  13.   @Column(name="airport_id", nullable=false)
  14.   public long getId() {
  15.     return id;
  16.   }
  17.   @Column(name="airport_name", nullable=false)
  18.   public String getName() {
  19.     return name;
  20.   }
  21.   @Column(name="locid", nullable=false)
  22.   public String getIata() {
  23.     return iata;
  24.   }
  25.   @Column(name="state", nullable=false)
  26.   public String getState() {
  27.     return state;
  28.   }
  29.   @Column(name="latitude", nullable=false)
  30.   public String getLat() {
  31.     return lat;
  32.   }
  33.   @Column(name="longitude", nullable=false)
  34.   public String getLng() {
  35.     return lng;
  36.   }
  37.   // The setter methods don't have an annotation on them. 
  38.   // They are not shown here, but they should be in the file
  39.   //   if you want to be able to change the values. 
  40. }

注意,一定要导入文件顶部的 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.zip991KBHTTP

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值