GORM绝对是Grails框架的一大亮点。GORM基于Hibernate的ORM之上做二次封装,既有Hibernate强大的功能,又有使用简便的特点。本篇主要总结和类比在项目开发中用到的GORM查询方式。

GORM支持的查询方式

GORM底层使用Hibernate,所以支持Hibernate的所有查询方式,此外,还支持Dynamic finder这种简便的查询。先对GORM支持的查询方式做一个表(参考来源: http://tatiyants.com/how-and-...:

dynamic finder where clause criteria HQL SQL
simple queries x x x x x
complex filters x x x x
associations x x x x
property comparisons x x x x
some subqueries x x x x
eager fetches w/ complex filters x x x
projections x x x
queries with arbitrary return sets x x
highly complex queries (like self joins) x
some database specific features x
performance-optimized queries x

从上表我们就能大致看出在什么样的场景下应该用什么类型的查询,每种查询方式具体的使用方式GORM参考官方文档。这里对这些查询方式简单做个总结

Dynamic finder

相信很多人第一眼对GORM的这个特性所惊艳到,可以用一种类似于静态方法的调用方式,就可以从数据库查询结果。它的优点和缺点一样明显,使用极为简单,功能也极为简陋,基本只能实现类似于:

SELECT * FROM domain WHERE condtions LIMIT x OFFSET y

这种方式的查询。只适合于单一Domain,查询条件固定的场景,也没有太多要说的,非常容易使用。

Book.findByName('bookName')
User.findAllByEnabled(true)
Persion.findAllByAgeBetween(10, 20)

Criteria

这个功能可以说是Hibernate的一个亮点,也是一个难点。它非常适合构造结构化查询的SQL,当查询条件不固定的时候,不需要在StringBuilder中编写大量的判断条件拼接的SQL,使得代码整洁度和可读性都大大提高。同时由于针对结构化查询的条件加了很多额外的方法,使得这个玩意对新手并不那么友好,有一定的上手门槛。另外也有一些查询上的限制,即使对SQL较为熟悉的用户,在写一个Criteria查询的时候可能也要想半天与调试一会。

Grails创建一个Criteria的语法有两种: Domain.createCriteria()Domain.withCriteria(Closure),后者可以看做是Domain.createCriteria().list(Closure)的别名。此外,由于Groovy的动态语言特性,所以Grails支持通过DSL的形式定义Criteria查询规则,而不需要像Hibernate那样写一堆.addXXX()的方法,比如:

User.createCriteria().list(params) {if (params.dateCreated) {gt('dateCreated', params.dateCreated)}if (params.status) {eq('status', params.status)}
}

list(Map params)支持的参数详见: http://docs.grails.org/latest...

代表要查询User这个Domain,但是dateCreatedstatus都是可选的查询条件,可能有也可能没有,通过Criteria的DSL可以很方便的定义这些查询条件,如果用HQL或者SQL写的话,是没有那么方便的,需要写判断条件,然后根据条件拼接对应的where语句,会让代码很冗长,而且四处拼接字符串也会让代码很难懂,一眼看不出来产生的SQL长什么样子。

此外,还可以加入projections,这样返回的就不是整个Domain对象了,而是Domain中指定的field或者field的聚合。相当于原本SQL中的SELECT * FROM domain变成了SELECT column1, count(column2), sum(column3),..., FROM domain。比如我要查询符合条件的用户数量以及平均年龄,可以这么写:

User.createCriteria().get {if (params.dateCreated) {gt('dateCreated', params.dateCreated)}if (params.status) {eq('status', params.status)}projections {count()avg('age')}
}

这样最终的结果就返回[count, avg]。如果Criteria的查询方式为list,并且传递有maxoffset参数的话,Grails会自动封装成一个PagedResultList对象,这个类中不但会包含符合条件的List,而且还会带有一个totalCount属性,便于分页查询,比如:

PagedResultList result = User.createCriteria().list([max: 10, offset: 10]) {if (params.dateCreated) {gt('dateCreated', params.dateCreated)}if (params.status) {eq('status', params.status)}
}List<User> = result.resultList
int totalCount = result.totalCount

如果开启了Hibernate的DEBUG及TRACE级别的日志,会发现这里其实执行了两条SQL语句,一条是按照where条件查询出符合条件的结果集,另一条是去掉order by之后的SELECT count(*) FROM domain WHERE conditions。也就是说,有分页查询的需求时,不需要自己写两条查询语句查询count + list了,写一个查询条件就行了,通过PagedResultList在Grails内部就会给你产生两条这样的SQL语句,减少了代码量。

此外还有一个更令人称道的特性,就是Criteria的DSL定义放在了groovy闭包中,因此可以利用闭包的动态delegate特性,复用查询条件。当需要复用Criteria的查询条件时,这个特性会变的特别好用。

还是上面那个例子,比如我们需要在用户查询的详情页面中返回符合查询条件的用户列表(支持分页),在dashboard统计页面中,返回用户数和平均年龄的统计就行了,我们会发现这个where条件是完全一样的,因此可以考虑复用:

Closure criteria = { Map params = [:] ->if (params.dateCreated) {gt('dateCreated', params.dateCreated)}if (params.status) {eq('status', params.status)}
}PagedResultList getUserList(Map params = [:]) {User.createCriteria().list(params) {criteria.delegate = delegatecriteria(params)}
}List<Integer> getUserSummary(Map params = [:]) {User.createCriteria().get {criteria.delegate = delegatecriteria(params)projections {count()avg('age')}}
|

这样在复用查询条件的时候,会让代码大大缩短,而且便于集中维护查询条件,而不是需要增加支持的查询条件的时候,所有调用的方法全改一遍。

NOTE: 直接这么使用时不支持并发的,如果这个闭包被同时delegate,并且使用的参数不一致,那么在GORM底层就会抛出java.util.ConcurrentModificationException,类似于这样:

java.util.ConcurrentModificationException: nullat java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)at java.util.ArrayList$Itr.next(ArrayList.java:859)at org.hibernate.loader.criteria.CriteriaQueryTranslator.getQueryParameters(CriteriaQueryTranslator.java:328)at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:109)at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1787)at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java:363)at org.grails.orm.hibernate.query.AbstractHibernateCriteriaBuilder.invokeMethod(AbstractHibernateCriteriaBuilder.java:1700)at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:931)at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:908)at org.grails.datastore.gorm.GormStaticApi$_withCriteria_closure11.doCall(GormStaticApi.groovy:384)at sun.reflect.GeneratedMethodAccessor633.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022)at groovy.lang.Closure.call(Closure.java:414)at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:54)at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:124)at com.sun.proxy.$Proxy111.doInSession(Unknown Source)at org.grails.datastore.mapping.core.DatastoreUtils.execute(DatastoreUtils.java:319)
...

此时,应考虑将这个闭包放到一个函数中,每次调用的时候返回一个新的闭包对象,这样就可以避免这个问题,比如:

Closure criteria() {{ Map params = [:] ->if (params.dateCreated) {gt('dateCreated', params.dateCreated)}if (params.status) {eq('status', params.status)}}
}

说了这么多Criteria的优点,也说说它的限制吧:

  • 查询的结果最终要绑定到Domain实例,或者返回Domain实例中的部分field或部分filed的聚合结果

所以不支持join结果。也就是说你需要SELECT domain1.c1, domain2.c2 FROM domain1 LEFT JOIN domain2 ON condtions这样的结果集的时候,是不能用Criteria实现的。

  • 跨Domain的查询条件必须是Domain的外键

比如定义了UserUserRole两个Domain:

class User {
}class UserRole {User userRole role
}

想查询角色下包含了哪些用户(一对多),如果用Criteria,只能这么查:

UserRole.withCriteria {role {eq('name', roleName)}projections {property('user')}
}

想用User去join UserRole是不可以的:

User.withCriteria {join('UserRole')  // invalid
}

因此在join上会有不小的限制,而HQL中就自由的多。

Detached Criteria

Detached Criteria的作用是仅构建查询条件,不执行查询,仅当调用了执行查询语句的方法(list, count, exists等等),才会真正执行查询。当一个查询条件可能要经过多道手续才能最终确认的时候,这个特性就比较有用了。另外在find and update这种情况下也比较有用:

def criteria = new DetachedCriteria(Person).build {eq 'lastName', 'Simpson'
}
def bartQuery = criteria.build {eq 'firstName', 'Bart'
}def results = bartQuery.list(max:4, sort:"firstName")

这里的DSL和Criteria的DSL是完全一样的,此外,还多了deleteAllupdateAll(Map)这样的方法。

where clause

where clause本质上返回的是一个Detached Criteria,所以理论上具有Detached Criteria的功能与限制,但是并不支持eager fetches这样的特性。本质上来看where clause查询可以看做是一个简化版本的Detached Criteria。

def query = Pet.where {year(birthDate) == 2011
}

和Criteria一样,在闭包中定义查询条件,但是where clause可以使用groovy的条件判断语法,而不是Criteria DSL的条件判断函数。比如下面这样的例子:

def results = Person.where {age > where { age > 18 }.avg('age')
}

很明显groovy的条件判断语句更灵活,不再局限于Criteria的DSL。最后必须提醒一下,where(Closure)本质上返回的是Detached Criteria,因此必须调用Detached Criteria的查询方法才会真正执行查询,如:

result.list()

HQL

用过Hibernate的用户对这个东西应该都不陌生,融合了SQL和Java对象的特性,并且可以支持join结果:

User.executeQuery('''FROM User userLEFT JOIN LoginHistory historyON history.user.id = user.idWHERE history.dateCreated >= LocalDateTime.now().minusDays(1)
''')

比如上面的结果可以返回[[User, LoginHistory], [User, LoginHistory], [],...[]]这样的join之后的结果,甚至这两个Domain可以没有直接的外键关联,因此需要复杂join的需求的时候,HQL会比Criteria方便的多,并且性能上也会比Criteria占优势。不过HQL对于查询条件不固定的需求就不那么友好了,同样需要很麻烦的拼接String。还需要注意的HQL支持的SQL标准很少,是所有SQL的子集,比如不支持postgresql的json query,不支持sql 99标准的CTE等等。

HQL的返回结果就不要求一定绑定Domain了,因此跨Domain的查询会比Criteria更灵活。

实际项目开发中,JOIN的操作应尽可能减少,防止未来出现大表JOIN这种严重拖慢性能的隐患。

SQL

这个其实最没啥要说的,熟悉RDB的用户对这玩意应该都会用,但是GORM的文档和Grails官方文档对怎么使用SQL却提之甚少。这里简单说一下怎么用SQL:

Book.withSession { Session session ->session.clear()
}

从任意一个Domain中通过withSession方法拿到Hibernate Session对象,然后通过Session的api就可以执行SQL了.

User.withSession { Session session ->session.createSQLQuery('SELECT * FROM user WHERE id = :id').setParameter('id', userId).addEntity(User)
}

我们只是用User中拿过来了Hibernate Session,这玩意非常底层了,并不会帮我们自动做ORM绑定,因此如果希望绑定到Domain中,必须加上.addEntity(Domain)方式进行绑定,当然也可以最后调用.list()让闭包直接返回List<ResultSet>结果。

使用SQL就可以使用数据库的全套特性了,是最自由的。同样这也将跟数据库耦合更紧密,你可能不能再切换底层数据库了。当需要数据库独有特性的时候,只能通过SQL解决了。比如写一个超长的SQL查询,使用CTE等高级特性。

不管怎样,在实际开发中还是应该尽可能避免直接使用这么底层的玩意,也应该尽可能减少直接使用SQL。

总结

本篇内容我们对Grails的GORM查询方式做了小结,对比了下各种查询方式的优缺点,实际项目中需要根据实际场景选择合适的使用方式。

虽然在ORM框架中应该尽可能避免使用底层的SQL,因为这会在一定程度上破坏框架的封装性,并且使用不当也会有SQL注入的风险。但是作为开发者,实际最应该熟练掌握的反而是最底层的SQL。

Grails GORM查询总结相关推荐

  1. 微服务追踪SQL(支持Isto管控下的gorm查询追踪)

    效果图 SQL的追踪正确插入到微服务的调用链之间 详细记录了SQL的执行内容和消耗时间 搜索SQL的类型 多线程(goroutine)下的追踪效果 在 Kubernetes 中部署微服务后,通过 Is ...

  2. Grails 复用查询条件并分页

    2019独角兽企业重金招聘Python工程师标准>>> 看过几篇写grails分页的,大都把简单的东西搞复杂了,而且里面都没有对查询条件复用,在分页时,求count和list的时候w ...

  3. 业精于勤荒于嬉---Go的GORM查询

    查询 //通过主键查询第一条记录 db.First(&user)SELECT * FROM users ORDER BY id LIMIT 1;// 随机取一条记录 db.Take(& ...

  4. Grails – GORM教程

    Grails被称为域驱动语言,这意味着我们可以使用从下到上的方法来构建应用程序,这种方法更接近面向对象的编程. GORM(Grail对象关系映射)使用Hibernate实习生通过表映射域,这赋予了域建 ...

  5. go学习 --- gorm查询

    一.条件查询 package mainimport ("fmt""github.com/jinzhu/gorm"_ "github.com/jinzh ...

  6. go : 使用gorm查询记录

    生活本沉闷 跑起来才有风 具体代码已放在:https://gitee.com/hjx_RuGuoYunZhiDao/strom-huang-go/blob/master/go_mysql/db_gro ...

  7. Grails GROM查询

    前文已经介绍了如何对Domain Class进行持久化,然而光存不查,那是没有什么意义的.因此,本篇将介绍持久化操作的另一面:查询. 最基本的查询有两种:罗列全部实例和获取单个实例,它们分别对应Dom ...

  8. 【Go语言实战】—— 时间戳转标准输出格式,标准输出转时间戳,gorm查询标准化时间

    支持将 time.Time(time.Now())格式的数据编码转换标准显示格式 支持将 整形时间戳(1603758504)的数据编码转换标准显示格式 源码地址 命令行安装: go get https ...

  9. gorm查询降序排列

    err = db.Limit(limit).Offset(offset).Order("id desc").Find(&releaseTestRecords).Error

最新文章

  1. Yolov3Yolov4核心基础知识完整讲解
  2. Effective C++ .07 virtual析构函数的提供
  3. Oracle PL/SQL进阶
  4. 《实践与思考》系列连载(6)——IT从业人员工作环境及状态调查 抽奖结果公布...
  5. python之Excel操作
  6. SSD性能怎么测?看这一篇就够了!
  7. 基于BLE + LoRa人员定位技术下的室内定位-Lora人员定位-新导智能
  8. java——记录一次条形码、二维码、订单自动生成的制作
  9. poj 1013 模拟 天平问题
  10. Codeforces 1633 E. Spanning Tree Queries ——暴力,kruskal,思维
  11. MAE TransMix
  12. Genymotion - 强大好用高性能的 Android 模拟器 (在电脑流畅运行APK安卓软件游戏的利器)
  13. win10定时关机c语言,win10系统通过命令实现定时关机的操作方法
  14. win10.java默认程序_Win10把IE修改为默认浏览器的两种方法
  15. 简练网软考知识点整理-蒙特卡洛模拟
  16. 为啥需要RPC,而不是简单的HTTP?
  17. 第二代计算机的主要应用领域,第二代计算机的主要应用领域是啥
  18. 在王者荣耀角度下分析面向对象程序设计B中23种设计模式之工厂方法模式
  19. 与计算机图形学相关的研究论文,计算机图形学论文
  20. 汉字转码和解码的使用

热门文章

  1. SAP 零售商品listing不成功,补充listing的方法
  2. 预训练后性能反而变差,自训练要取代预训练了吗?
  3. 文科生都能看懂的机器学习教程:梯度下降、线性回归、逻辑回归
  4. 人工智能如何落地安防?需先迈过算力这一关
  5. SAP QA32 做使用决策系统报错:分类数据的不一致性=交易终止
  6. 就业丨2018年国内就业薪资高的5大编程语言排行
  7. 《科学》:中国科学家揭示,人脑中间神经元多样性从何而来?
  8. 2021年突破人类想象力的6大科学纪录
  9. DARPA新局长维多利亚·科尔曼展望未来发展
  10. 人工智能如何推动神经科技发展?