Grails 1.2参考文档速读(8):GORM中的查询

前文已经介绍了如何对Domain Class进行持久化,然而光存不查,那是没有什么意义的。因此,本篇将介绍持久化操作的另一面:查询。

最基本的查询有两种:罗列全部实例和获取单个实例,它们分别对应Domain Class的list和get方法。使用例子如下:

  • 列举全部:Book.list()
  • 分页并排序(这些参数不需要同时出现,按需使用即可):Book.list(offset:10, max:20, sort:"asc", order:"title")
  • 获取单个实例:Book.get(23)
  • 获取多个实例:Book.getAll(23, 93, 81)

list只能无条件的列出实例,但在日常开发中,最常见的就是按条件列出实例。一般情况下,我们都是直接通过HQL或SQL来完成,而在此处,借助Groovy的动态语言特性,我们可以使用动态查找器来优雅地完成这一任务。

动态查找器由前缀为findBy和findAllBy的方法构成,这些方法的构造规则(以findBy为例):DomainClass.findBy([Property][Comparator] [Boolean Operator])?[Property][Comparator]。其中:

  • 属性即为Domain Class的属性
  • 比较符有:InList、LessThan、LessThanEquals、GreaterThan、GreaterThanEquals、Like、Ilike(类似like,大小写不敏感)、NotEqual、Between、IsNotNull、IsNull
  • 布尔操作符有:and和or

请注意,这些方法并不是一开始就存在的,它们是利用Groovy MOP在运行时动态产生的,关于Groovy MOP的详细介绍,请阅读《Programming Groovy》。

同时,这类方法可以应用于多个属性,只要按照同样的规则自行购建即可。这里有一些动态查找器的例子:

  • Book.findByTitle("The Stand")
  • Book.findByReleaseDateBetween( firstDate, secondDate )
  • Book.findAllByTitleLikeOrReleaseDateGreaterThan("%Java%", new Date()-30)

动态查找器同样可以对关联进行查询,例子如下:

    def author = Author.findByName("Stephen King")
    def books = author ? Book.findAllByAuthor(author) : []

而且,还可以进行分页和排序,参数同前:Book.findAllByTitleLike("Harry Pot%", [max:3, offset:2, sort:"asc", order:"title"])

Criteria是Hibernate的一个重要功能,GORM当然不会忽略它。通过Domain Class的createCriteria或withCriteria方法即可使用Criteria。这两个方法返回的是HibernateCriteriaBuilder,利用这个Builder,我们可以来构造criteria查询。关于这个对象,请查看参考文档。它的使用如下:

    def c = Account.createCriteria()
    def results = c {
        like("holderFirstName", "Fred%")
        and {
            between("balance", 500, 1000)
            eq("branch", "London")
        }
        maxResults(10)
        firstResult(50)
        fetchMode("aRelationship", FM.EAGER)
        order("holderLastName", "desc")
    }

对于关联的查询:

    def c = Account.createCriteria()
    def results = c.list {
         or {
            between('created',now-10,now)
            //transactions是Account中的hasMany关联属性
            transactions {
                 between('date',tDate-10, tDate)
            }
         }
    }

投影:

    def c = Account.createCriteria()
    //get方法只返回一条记录
    def numberOfBranches = c.get {
        projections {
            countDistinct('branch')
        }
    }

Hibernate里面我们可以直接使用SQL约束,这里也不例外,使用它时请注意对应用移植性的影响:

    def c = Person.createCriteria()
    def peopleWithShortFirstNames = c.list {
        //这里面使用的是SQL
        sqlRestriction "char_length( first_name ) <= 4"
    }

缺省返回的结果集是无法来回移动游标的,如果有这样的需求,那么可以求助于滚动结果集:

    def results = crit.scroll {
          maxResults(10)
    }
    def f = results.first()
    def l = results.last()
    def n = results.next()
    def p = results.previous()
    def future = results.scroll(10)
    def accountNumber = results.getLong('number')

我们还可以设置查询的分页、排序和Fetch模式:

    import org.hibernate.FetchMode as FM
    ......
    def results = c.list {
        maxResults(10)
        firstResult(50)
        fetchMode("aRelationship", FM.EAGER)
    }

设置Eager取(注意join的使用):

    def criteria = Task.createCriteria()
    def tasks = criteria.list{
         eq "assignee.id", task.assignee.id
         join 'assignee'
         join 'project'
         order 'priority', 'asc'
    }

对于熟悉SQL的开发者,HQL是他们感到最亲切的东西。我们同样也可以在Domain Class中使用,通过find和findAll即可。前者返回匹配的第一个值,后者则返回全部:

  • Book.find("from Book as b where b.author='Dan Brown'")
  • Book.find(new Book(author:"Dan Brown")),这也是一个QBE(Query by Example)的例子
  • Book.findAll()
  • Book.findAll("from Book as b where b.author=? order by b.releaseDate",['Dan Brown'],[max:10,offset:5])

如果查询比较简单,我们可以直接提供Where部分,使用findWhere和findAllWhere即可(分页和排序的参数同前。)。

  • Author.findWhere(name:'Stephen King')
  • Author.findWhere(name:'Stephen King')

Groovy中支持多行字符串,对此要说明Groovy的多行字符串不能直接用于GORM,得要有点变动(注意每行结尾):

    def results = Book.findAll("""\
        from Book as b, \
            Author as a \
        where b.author = a and a.surname = ?""", ['Smith'])

除了find,你还可以使用executeQuery:Book. executeQuery("from Book as b where b.title like 'Lord of the%'")。

至此,GORM的查询就介绍完毕了,在下一篇中我们将迎来GORM部分的最后一部分内容:高级特性、编程性事务和约束。


本系列的其他文章:

By foxgem - Posted on 16 一月 2010

关系中的查询findBy*可用么?

比如,两个Domain是多对多的关系:

class Tag {
String text
static belongsTo = Snippet
static hasMany = [snippets:Snippet]
}

class Snippet {
String title
String text
  static hasMany = [tags:Tag] 
}
 那么如下的查询是否可行?

 def tag = Tag.get(1)

def results = Snippet.findAllByTags(tag)
 

实验了一下,貌似不可以,输出的sql为:

Hibernate:
select
tag0_.id as id5_0_,
tag0_.version as version5_0_,
tag0_.text as text5_0_
from
tag tag0_
where
tag0_.id=?

Hibernate:
select
this_.id as id7_0_,
this_.version as version7_0_,
this_.text as text7_0_,
this_.title as title7_0_
from
snippet this_
where
this_.id=?
 

我想,同样,一对一、一对多,类似的查询应该也不可行。

对于关系的1端,findBy是可用的。

以下的实验代码可以在Grails Console里运行:

import groovyq.*

def parent= new Parent(name:'parent')
def child1= new Child(name:'child1')
def child2= new Child(name:'child2')

parent.addToChildren child1
parent.addToChildren child2
parent.save()

Child.findAllByParent(parent)

其中,Child和Parent是多对一的例子,如此的话,对于1-1应该也可以。Grails文档中的例子也给出的是对于1端的查询。

也就是说parent.findAllByChildren是

也就是说parent.findAllByChildren是不可以的,多谢了!

一般来讲,Parent.findAllByChildren,似乎也没有什么意义。

一般来讲,Parent.findAllByChildren,似乎也没有什么意义。更常见的是已知某个child,而去找parent。

請問若是trasnsient屬性 又要如何查詢呢

class CdApplicant {
    String name 
    Integer age  

    static transients = ['age']

    def getAge(){//算足歲 齡
        int age
        if (birthday){
            Date today = new Date()
            age = Integer.parseInt(today.format('yyyy'))  - Integer.parseInt( birthday.format('yyyy'))
            if (today.format('MMdd') < birthday.format('MMdd') ){
              age = age - 1
            }
        }
        return age
    }

}

如age 就無法查詢?請問要用什麼方法來查呢?謝謝^^

试试派生属性是否满足你的需要

若是使用transients,是无法查询的。但是通过派生属性可以,但其中就得使用SQL了,把你的这部分逻辑可以移入到数据库中,详细的做法可以参见Grails 1.3.x的参考文档“5.5.2.11 Derived Properties”,里面有个例子。

 Derived Properties

 Derived Properties 可以被查詢,但因為我算age足歲公式較複雜而且又只能用SQL code,所以像我上面的公式,好像也沒有辦法列到 

static mapping = { age formula: '算足歲的公式一大串' }

formula這裡好像能只作一簡單的加減乘除

請問 Derived Properties是我理解的這樣做用法嗎?

非也。

注意文档里的这样一句话: the formula expressed in the ORM DSL is SQL,不限于加减,还可以是目标数据库的sql函数。

因此我认为,派生属性最后在hibernate取数据时,联合其他域属性,最终的查询语句应该类似:

select c1, c2, c3, 派生属性的sql(即formula的值) from 领域类对应的表

我前面说的把逻辑移入到数据库中的意思是,以oracle为例,你可以把计算age的那个方法写成一个自定义的函数,然后在派生属性里去调用。

以上主要是我的推理,没有亲手试过,但根据文档的意思应该差不多的。此外,这个实际是hibernate的功能,你也可以参考hibernate的文档。

关于这个速读的建议

我都是把这个速度当作参考书的,可是每次都要找半天才能找到这个系列。能否提供一个快捷方式能直接访问到这个系列的所有博文?

新增了“资源”链接

已经新增了“资源”链接,方便阅读。非常感谢你的建议!