`
hax
  • 浏览: 969959 次
  • 性别: 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 了。

相关推荐

    gorm-standalone-example-2_4_x:Gorm独立示例-Spring Boot

    它是适用于Grails 2.4.x的(2.3版之前的Grails 2.x)项目的延续。 Spring Boot用于处理配置和引导。 参考 笔记 这是一个快速的实验,仅在JDK7 OSX上进行了测试。 被用作构建系统。 配置和数据 以下是该项目的...

    一个基于Qt Creator(qt,C++)实现中国象棋人机对战

    qt 一个基于Qt Creator(qt,C++)实现中国象棋人机对战.

    热带雨林自驾游自然奇观探索.doc

    热带雨林自驾游自然奇观探索

    冰川湖自驾游冰雪交融景象.doc

    冰川湖自驾游冰雪交融景象

    C51 单片机数码管使用 Keil项目C语言源码

    C51 单片机数码管使用 Keil项目C语言源码

    基于智能算法的无人机路径规划研究 附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    前端分析-2023071100789s12

    前端分析-2023071100789s12

    Delphi 12.3控件之Laz-制作了一些窗体和对话框样式.7z

    Laz_制作了一些窗体和对话框样式.7z

    ocaml-docs-4.05.0-6.el7.x64-86.rpm.tar.gz

    1、文件内容:ocaml-docs-4.05.0-6.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/ocaml-docs-4.05.0-6.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、更多资源/技术支持:公众号禅静编程坊

    学习笔记-沁恒第六讲-米醋

    学习笔记-沁恒第六讲-米醋

    工业机器人技术讲解【36页】.pptx

    工业机器人技术讲解【36页】

    基于CentOS 7和Docker环境下安装和配置Elasticsearch数据库

    内容概要:本文档详细介绍了在 CentOS 7 上利用 Docker 容器化环境来部署和配置 Elasticsearch 数据库的过程。首先概述了 Elasticsearch 的特点及其主要应用场景如全文检索、日志和数据分析等,并强调了其分布式架构带来的高性能与可扩展性。之后针对具体的安装流程进行了讲解,涉及创建所需的工作目录,准备docker-compose.yml文件以及通过docker-compose工具自动化完成镜像下载和服务启动的一系列命令;同时对可能出现的问题提供了应对策略并附带解决了分词功能出现的问题。 适合人群:从事IT运维工作的技术人员或对NoSQL数据库感兴趣的开发者。 使用场景及目标:该教程旨在帮助读者掌握如何在一个Linux系统中使用现代化的应用交付方式搭建企业级搜索引擎解决方案,特别适用于希望深入了解Elastic Stack生态体系的个人研究与团队项目实践中。 阅读建议:建议按照文中给出的具体步骤进行实验验证,尤其是要注意调整相关参数配置适配自身环境。对于初次接触此话题的朋友来说,应该提前熟悉一下Linux操作系统的基础命令行知识和Docker的相关基础知识

    基于CNN和FNN的进化神经元模型的快速响应尖峰神经网络 附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    网络小说的类型创新、情节设计与角色塑造.doc

    网络小说的类型创新、情节设计与角色塑造

    毕业设计-基于springboot+vue开发的学生考勤管理系统【源码+sql+可运行】50311.zip

    毕业设计_基于springboot+vue开发的学生考勤管理系统【源码+sql+可运行】【50311】.zip 全部代码均可运行,亲测可用,尽我所能,为你服务; 1.代码压缩包内容 代码:springboo后端代码+vue前端页面代码 脚本:数据库SQL脚本 效果图:运行结果请看资源详情效果图 2.环境准备: - JDK1.8+ - maven3.6+ - nodejs14+ - mysql5.6+ - redis 3.技术栈 - 后台:springboot+mybatisPlus+Shiro - 前台:vue+iview+Vuex+Axios - 开发工具: idea、navicate 4.功能列表 - 系统设置:用户管理、角色管理、资源管理、系统日志 - 业务管理:班级信息、学生信息、课程信息、考勤记录、假期信息、公告信息 3.运行步骤: 步骤一:修改数据库连接信息(ip、port修改) 步骤二:找到启动类xxxApplication启动 4.若不会,可私信博主!!!

    57页-智慧办公园区智能化设计方案.pdf

    在智慧城市建设的大潮中,智慧园区作为其中的璀璨明珠,正以其独特的魅力引领着产业园区的新一轮变革。想象一下,一个集绿色、高端、智能、创新于一体的未来园区,它不仅融合了科技研发、商业居住、办公文创等多种功能,更通过深度应用信息技术,实现了从传统到智慧的华丽转身。 智慧园区通过“四化”建设——即园区运营精细化、园区体验智能化、园区服务专业化和园区设施信息化,彻底颠覆了传统园区的管理模式。在这里,基础设施的数据收集与分析让管理变得更加主动和高效,从温湿度监控到烟雾报警,从消防水箱液位监测到消防栓防盗水装置,每一处细节都彰显着智能的力量。而远程抄表、空调和变配电的智能化管控,更是在节能降耗的同时,极大地提升了园区的运维效率。更令人兴奋的是,通过智慧监控、人流统计和自动访客系统等高科技手段,园区的安全防范能力得到了质的飞跃,让每一位入驻企业和个人都能享受到“拎包入住”般的便捷与安心。 更令人瞩目的是,智慧园区还构建了集信息服务、企业服务、物业服务于一体的综合服务体系。无论是通过园区门户进行信息查询、投诉反馈,还是享受便捷的电商服务、法律咨询和融资支持,亦或是利用云ERP和云OA系统提升企业的管理水平和运营效率,智慧园区都以其全面、专业、高效的服务,为企业的发展插上了腾飞的翅膀。而这一切的背后,是大数据、云计算、人工智能等前沿技术的深度融合与应用,它们如同智慧的大脑,让园区的管理和服务变得更加聪明、更加贴心。走进智慧园区,就像踏入了一个充满无限可能的未来世界,这里不仅有科技的魅力,更有生活的温度,让人不禁对未来充满了无限的憧憬与期待。

    一种欠定盲源分离方法及其在模态识别中的应用 附Matlab代码.rar

    1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

Global site tag (gtag.js) - Google Analytics