Groovy 技巧
如前所述,Groovy 的动态特性允许我通过 get和 set Property方法捕获对不存在的属性的方法调用。这样,清单 2 中的 Model的子类不必定义它们自己的属性 —它们只是将对一个属性的所有调用委托给这个底层 entity对象。
清单 2 中的代码执行了一些特定于 Groovy 的操作,值得一提。首先,可以通过在一个属性前面附加一个 @来绕过该属性的访问器方法。我必须对构造函数中的 entity对象引用执行上述操作,否则我将调用 setProperty方法。很明显,在这个关头调用 setProperty将打破这种模式,因为 setProperty方法中的 entity变量将是 null。
其次,构造函数中的调用 this.getClass().simpleName将设置 entity的 “种类” — simpleName属性将生成一个不带包前缀的子类名称(注意,simpleName的确是对 getSimpleName的调用,但 Groovy 允许我不通过对应的 JavaBeans 式的方法调用来尝试访问一个属性)。
最后,如果对 id属性(即,对象的键)进行一个调用,getProperty方法很智能,能够询问底层 key以获取它的 id。在 Google App Engine 中,entities的 key属性将自动生成。
Race 子类
定义 Race子类很简单,如清单 3 所示:
清单 3. 一个 Race 子类
class Race extends Model {
public Race(params){
super(params)
}
}
当一个子类使用一列参数(即一个包含多个 “键 / 值” 对的 Map)实例化时,一个对应的 entity将在内存中创建。要持久存储它,只需调用 save方法。
清单 4. 创建一个 Race 实例并将其保存到 GAE 的数据存储
def iparams = [:]
def formatter = new SimpleDateFormat("MM/dd/yyyy")
def rdate = formatter.parse("04/17/2010")
iparams["name"] = "Charlottesville Marathon"
iparams["date"] = rdate
iparams["distance"] = 26.2 as double
def race = new Race(iparams)
race.save()
清单 4 是一个 Groovlet,其中,一个 Map(称为 iparams)创建为带有 3 个属性 —一次比赛的名称、日期和距离。(注意,在 Groovy 中,一个空白 Map通过 [:]创建。)Race的一个新实例被创建,然后通过 save方法存储到底层数据存储。
可以通过 Google App Engine 控制台来查看底层数据存储,确保我的数据的确在那里,如图 2 所示:
图 2. 查看新创建的 Race
查找程序方法生成持久存储的实体
现在我已经存储了一个 Entity,拥有查找它的能力将有所帮助。接下来,我可以添加一个 “查找程序” 方法。在本例中,我将把这个 “查找程序” 方法创建为一个类方法(static)并且允许通过名称查找这些 Race(即基于 name属性搜索)。稍后,总是可以通过其他属性添加其他查找程序。
我还打算对我的查找程序采用一个惯例,即指定:任何名称中不带单词 all的查找程序都企图找到 一个实例。名称中包含单词 all的查找程序(如 findAllByName)能够返回一个实例 Collection或 List。清单 5 展示了 findByName查找程序:
清单 5. 一个基于 Entity 名称搜索的简单查找程序
def query = new Query(Race.class.simpleName)
query.addFilter("name", Query.FilterOperator.EQUAL, name)
def preparedQuery = this.datastore.prepare(query)
if(preparedQuery.countEntities() > 1){
return new Race(preparedQuery.asList(withLimit(1))[0])
}else{
return new Race(preparedQuery.asSingleEntity())
}
}
这个简单的查找程序使用 Google App Engine 的 Query和 PreparedQuery类型来查找一个类型为 “Race” 的实体,其名称(完全)等同于传入的名称。如果有超过一个 Race符合这个标准,查找程序将返回一个列表的第一项,这是分页限制 1(withLimit(1))所指定的。
对应的 findAllByName与上述方法类似,但添加了一个参数,指定 您想要的实体个数,如清单 6 所示:
清单 6. 通过名称找到全部实体
def query = new Query(Race.class.getSimpleName())
query.addFilter("name", Query.FilterOperator.EQUAL, name)
def preparedQuery = this.datastore.prepare(query)
def entities = preparedQuery.asList(withLimit(pagination as int))
return entities.collect { new Race(it as Entity) }
}
与前面定义的查找程序类似,findAllByName通过名称找到 Race实例,但是它返回 所有 Race。顺便说一下,Groovy 的 collect方法非常灵活:它允许删除创建 Race实例的对应的循环。注意,Groovy 还支持方法参数的默认值;这样,如果我没有传入第 2 个值,pagination将拥有值 10。
清单 7. 查找程序的实际运行
assert nrace.distance == 26.2
def races = Race.findAllByName("Charlottesville Marathon")
assert races.class == ArrayList.class
清单 7中的查找程序按照既定的方式运行:findByName返回一个实例,而 findAllByName返回一个 Collection(假定有多个 “Charlottesville Marathon”)。