`
hax
  • 浏览: 962527 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Grails陷阱之二

    博客分类:
  • MISC
阅读更多
前篇:Grails陷阱之一

Grails陷阱之二:跨request使用Domain Class实例需要重新attach

其实这并不能说是Grails的陷阱,而是Grails所依赖的Hibernate设计使然,不过初学者(比如我)可能对此没有概念,因此拿来说一下。

代码非常简单,如下:
if (session.user.canDo(actionName)) {
    ...
}

这段代码从session中取出user(假设user登陆之后,你把user对象保存在HTTP session中),然后调用上面的canDo方法,检测user是否有权限执行某个action。canDo方法会读取user.roles(一对多)。

扔出异常如下:
Grails Runtime Exception
Error Details
Error 500: could not initialize proxy - no Session
Servlet: grails
URI: /test/grails/admin/index.dispatch
Exception Message: could not initialize proxy - no Session
Caused by: could not initialize proxy - no Session
Class: AuthFilters
At Line: [40] 


注意,这个异常仅会在你的代码(比如canDo)访问一对一或一对多的关联对象时产生。

熟悉hibernate的同志可能一眼就看出是什么问题了。在访问关联对象时,Hibernate通过动态代理来读取以便支持lazy加载,而延迟加载的相关操作同所有持久化操作一样,需要在一个session内(是Hibernate的持久化Session,不要和Web容器管理的HTTP Session混淆哦),而当一个持久化对象被保存到HTTP Session中,然后在下一个request中被拿出来时,当然之前的Hibernate的Session早结束了。

注意,Hibernate提供了OSIV(Open Session In View)的Filter,可以配置在web.xml中,会针对每个HTTP request,开启一个新的Hibernate session,然后在request结束时关闭。

如果没有OSIV,你也会遇到类似的异常,但是这和这里讨论的并不是一个问题——我为什么对这个陷阱印象深刻,就是因为我一度把这两种情况搞混了。

Grails已经内置了OSIV,虽然它可能存在一些bug(见后文),但是这里的问题并非如此。这里的问题是,在新一次request中的Hibernate session,已经不是创建user对象当初的那个Hibernate session了。

解决方案:

1. 不在session中保存domain class的实例(即持久化对象),而是保存它的id。
if (User.read(session.userId).canDo(actionName)) {
    ...
}

我见到的类似的方式还有
session.user = User.read(session.user.id)
if (session.user.canDo(actionName)) {
    ...
}

当然这样的代码太别扭,而且只适用User.read(即只读)的情形,如果你之前对user对象做过修改(比如loginCount++)并尚未保存,则重新读取会丢失原对象上的修改。

2. 不使用lazy load,所有关联都一次读入。
class User {
    ...
    static hasMany = [roles:Role]
    static mapping = {
        roles lazy:false
    }
}

注意,调用时所有可能读到的关联都必须改成不是lazy的的,比如假设你的canDo方法,还要检查每个roles上的permission,则permissions关联也要禁用lazy加载:
class User {
    ...
    static hasMany = [roles:Role]
    static mapping = {
        roles lazy:false
    }

    boolean canDo(String action) {
        roles.any {
	    it.permissions.contains('*') || 
	    it.permissions.contains(action) 
        }
    }
}
class Role {
    ...
    static hasMany = [permissions:String]
    static mapping = {
        permissions lazy:false
    }
}



方案1的问题就是它只适用于只读情况,如果需要跨若干个request修改持久化对象就不行了。当然你可以把所有修改存放在web session中,然后一次性修改和提交,但是这无谓增加了代码复杂度。方案2也只适用于只读情况,还要求你小心处理所有的关联。而且在许多情况下不宜一次性读入所有相关数据(否则干嘛要lazy加载)。幸好,我们有方案3。

3. 重新attach
if (!session.user.attached) session.user.attach()
if (session.user.canDo(actionName)) {
    ...
}

Grails的domain class上的attach方法,可以把一个持久化对象重新attach到当前的Hibernate Session中。

类似的方式还有调用domainInstance.refresh(),但它会强制重新读取数据库,所以通常不应使用。

还有domainInstance = domainInstance.merge(),它相当于detached版本的save()。注意merge返回一个新对象,所以要重新给引用赋值。


就我自己的案例来说,由于user对象是每次都要用的,所以我最后使用了Grails filters来重新attach:

class AuthFilters {

	def filters = {
		
		login(controller:'*', action:'*') {
			before = {
				if (controllerName && controllerName != 'auth' && !session.user) {
					redirect(controller:'auth', action:'login',
						params:[url:request.forwardURI])
					return false
				} else if (session.user && !session.user.attached) {
					log.debug "reattach ${session.user}"
					session.user.attach()
				}
			}
		}
	}
}


其他:

不过你还是有可能会在layout gsp中碰到类似的问题(我暂时还没遇到),据说是grails和sitemesh的整合bug,将在grails 1.2中解决。
分享到:
评论
5 楼 tedeyang 2012-07-13  
hibernate这种框架迟早要进历史的垃圾堆啊
4 楼 wintersun 2009-05-02  
} else if (session.user && !session.user.attached) {   
                    log.debug "reattach ${session.user}"  
                    session.user.attach()   
                }


有点奇怪,如果我用“user.attached”就会报出以下错误

org.codehaus.groovy.runtime.InvokerInvocationException: groovy.lang.MissingPropertyException: No such property: attached for class: xxx


改用user.isAttached()就可以了。
3 楼 fcoffee 2009-03-26  
""注意,Hibernate提供了OSIV(Open Session In View)的Filter""

OSIV是Spring提供的, 不是hibernate
2 楼 hax 2009-03-19  
SSailYang 写道

感觉你 attach 一个 Model 的原因无非是因为 LazyInitializationException。如果 User 的 roles 要经常适用的话,比如是因为权限的操作,那可以用 join fetch 直接读取出来,这样也不用 attach 了。


lazy:false即相当于join fetch,即我写的第二种方法。如文中所述,这样只适合只读场合,如果role在运行时被改变了,采用第二种方式是不会立即起作用的,需要重新登陆,或提供强制刷新机制。更关键的问题是,这样的代码存在潜在的异常可能——比如你加了一段代码,访问了原先没有访问的某个lazy关联——而且这种异常是一般的单元测试无法测试出来的。所以attach是更好的做法。
1 楼 SSailYang 2009-03-19  
感觉你 attach 一个 Model 的原因无非是因为 LazyInitializationException。如果 User 的 roles 要经常适用的话,比如是因为权限的操作,那可以用 join fetch 直接读取出来,这样也不用 attach 了。

相关推荐

    Grails Grails Grails

    Grails 是一个基于 Groovy 语言的开源Web应用程序框架,它构建在Java平台之上,旨在简化开发过程并提高生产力。Grails 的设计深受Ruby on Rails的影响,提供了MVC(模型-视图-控制器)架构模式,允许开发者快速构建...

    Grails开发之(Rest教程).pdf

    标题中提到的"Grails开发之(Rest教程)"表明本文是一份关于Grails框架下进行RESTful服务开发的教程。Grails是一个使用Groovy语言编写的高生产力的框架,其使用约定优于配置的理念,允许快速开发Web应用程序。...

    the definitive guide to grails 2

    Grails框架是建立在Groovy编程语言之上的一个完整的Web应用开发框架,它结合了Java平台的优势和动态语言的灵活性。Grails采用约定优于配置的原则,简化了开发流程,使开发者能够快速地构建高性能的Web应用程序。该...

    Grails权威指南 Grails权威指南

    2. **MVC架构**:Grails采用MVC模式组织应用程序,将业务逻辑、用户界面和数据访问分离,有利于团队协作和代码复用。在Grails中,Controller处理请求,View呈现结果,Model则存储和管理业务数据。 3. **GORM...

    Grails开发之(Rest教程).docx

    在Grails中开发RESTful API是一项常见的任务,这个文档提供了基于Grails 3.1.5版本的REST教程。Grails是一种基于Groovy语言的开源Web应用框架,它利用了Spring Boot的功能,使得构建现代互联网应用程序变得更加高效...

    grails2

    grails-2

    grails 中文第二版

    - GORM是Grails的核心特性之一,用于处理对象与数据库之间的映射。 - 支持基本的CRUD操作。 **领域(Domain)建模** - 领域类是GORM的基础,代表数据库中的实体。 - 支持多种关联方式,如多对一、一对多等。 - 支持...

    Eclipse下搭建Grails项目

    2. **Eclipse安装** - 由于Grails项目涉及GSP(Groovy Server Pages)文件,推荐使用包含JSP编辑器的Eclipse JEE版本。如果你对JSP语法熟悉且内存有限,也可选择Classic版本。 - 安装Eclipse 3.4.0 JEE版本或其他...

    grails-用户手册

    2. View:视图层负责展示数据,Grails支持多种模板引擎,如GSP(Groovy Server Pages),可以混合Groovy代码和HTML来构建动态页面。 3. Controller:控制器层接收用户请求,调用模型进行业务处理,并决定视图如何...

    grails脚手架2次优化

    《Grails脚手架二次优化详解》 在Web开发领域,Grails框架以其高效和便捷的特性深受开发者喜爱。其中,脚手架(Scaffolding)是Grails提供的一种快速开发工具,它能够自动生成基本的CRUD操作,极大地提高了开发效率...

    Grails从入门指南(第二版)

    《Grails从入门指南(第二版)》是一本专为初学者设计的全面教程,旨在帮助读者快速掌握Grails框架的基础知识和高级特性。Grails是一个基于Groovy语言的开源Web应用开发框架,它简化了Java平台上的开发过程,提供了...

    eclipse开发grails插件

    2. **安装Grails插件**:有了GroovyEclipse的支持,我们还需要安装Grails插件。同样地,可以通过Eclipse的"Help" -> "Install New Software",添加Grails插件的更新站点(如:...

    Grails入门教程(二)

    Grails 入门教程(二) 一、建立域关系 在 Grails 框架中,建立域关系是指定义对象之间的关系。一个域对象可以拥有多个其他域对象的引用,这些引用可以是“一对一”、“一对多”或“多对多”的关系。例如,在实例...

    [Grails] Grails 2 权威指南 (英文版)

    [Apress] Grails 2 权威指南 (英文版) [Apress] The Definitive Guide to Grails 2 (E-Book) ☆ 出版信息:☆ [作者信息] Jeff Scott Brown, Graeme Rocher [出版机构] Apress [出版日期] 2013年01月23日 ...

    grails快速开发web

    #### 二、Grails 的核心特性 - **面向领域语言 (DSL)**:Grails 提供了一种面向领域的语言,让开发者能够用更少的代码完成更多的工作。 - **自动配置**:Grails 自动处理很多配置细节,如数据源、日志管理等,减少...

    grails-2.4.4.zip

    Grails 的强大之处在于其丰富的插件库,如Spring Security用于安全控制,Hibernate Search提供全文搜索功能,以及各种用于支付、邮件发送、社交网络集成的插件,极大地扩展了框架的功能。 6. **IDE集成** ...

    grails 中文文档+grails-fckeditor-0.9.5.zip插件

    3. Convention over Configuration(CoC):Grails的核心理念之一就是“约定优于配置”,这意味着开发者在很多情况下不需要写大量的配置文件,框架会自动根据约定进行工作。 二、Grails中文文档的价值 1. 学习入口...

    grails中文入门简介

    Grails是一个基于Groovy语言的全栈框架,它遵循约定优于配置的原则,并且紧密集成Spring和Hibernate等流行的Java库,简化了开发流程。Grails在IT行业中尤其受到重视,因为它能够帮助开发者快速搭建并部署基于MVC模式...

Global site tag (gtag.js) - Google Analytics