【问题标题】:Grails 2.5.6 transaction RollbackGrails 2.5.6 事务回滚
【发布时间】:2017-11-02 10:13:14
【问题描述】:

如果发生错误或域类无效,我要做的主要事情是返回编辑页面。 我有一个 User 对象,其中包含一个 Person 对象作为属性。

我的控制器如下所示:

Object update(){
     User.withTransaction{status ->
        try {
            personDaoService?.bindObject(person, params)
            person.validate()
        } catch(Exception e){
            log.error(e.printStackTrace()) 
            status.setRollbackOnly()
        }

        if(person.hasErrors()){ 
            status.setRollbackOnly()
        }
        if(status.isRollbackOnly()){
            person.discard()
            person.refresh()
            return render(view: 'edit', model:[user:user, person:person], params: params)
        }

       try {
            userDaoService?.bindObject(person, params)
            user.validate() 
        } catch(Exception e){
            log.error(e.printStackTrace()) 
            status.setRollbackOnly()
        } 

        if(status.isRollbackOnly()){
            user.discard()
            user.refresh()
            return render(view: 'edit, model:[user:user, person:person], params: params)
        }       
    }
}

返回编辑页面时,我总是收到一个异常,告诉我没有打开的会话来加载人员对象。

为了快速解决这个问题,我在用户对象的映射中添加了lazy:false,但这导致整个项目运行非常缓慢,因此我不得不删除lazy:false 映射选项。

现在我正试图找到一种方法让它再次工作。

为什么 hibernate 在回滚时关闭会话?

如何重新打开会话?

编辑:

我尝试在设置回滚之前渲染视图:

if(person.hasErrors()){                        
    person.errors.allErrors.each{user.errors.rejectValue("person", "", "${validationTagLib.message(error: it)}")}
    render(view: 'edit', model: [user: user], params: params)
    status.setRollbackOnly()
    return
}

这会导致此异常: org.hibernate.LazyInitializationException: could not initialize proxy - no Session

【问题讨论】:

  • 您可以在 status.setRollbackOnly() 之前将人和用户对象/数据存储在一个全局变量中,这样就可以用来渲染您的视图。
  • 也可以使用@Transactional注解来更新方法
  • 不相关,但log.error(e.printStackTrace())没有按照您的想法行事。 e.printStackTrace() 是一个void 方法,它将堆栈跟踪转储到标准输出,因此您只记录null。更改为log.error(e.message, e)(替换或增加e.message),它将记录错误消息和堆栈跟踪
  • 将对象存储在全局变量中不起作用,@Transactional 注释也不起作用,两者都导致此错误org.hibernate.LazyInitializationException: could not initialize proxy - no Session
  • 通过在失败的方法周围添加一个 try and catch 来捕获可抛出的错误 - 首先让页面通过,就好像没有出错一样 - 不是你想要的 - 但会帮助你找到问题并希望修复它 - 我有时会看到这种情况,通常是当你要求的事情无法完成时 - 通常不仅仅是由验证等引起的特定错误

标签: hibernate grails


【解决方案1】:

我在 DomainClasses 中创建了一个方法来加载所有需要的对象,我在返回编辑页面时调用了这个方法:

 Object update(){
     User.withTransaction{status ->
        try {
            personDaoService?.bindObject(person, params)
            person.validate()
        } catch(Exception e){
            log.error(e.printStackTrace()) 
            status.setRollbackOnly()
        }

        if(person.hasErrors()){ 
            status.setRollbackOnly()
        }
        if(status.isRollbackOnly()){
            person.discard()
            person.refresh()
            loadObject(person)
            return render(view: 'edit', model:[user:user, person:person], params: params)
        }

       try {
            userDaoService?.bindObject(person, params)
            user.validate() 
        } catch(Exception e){
            log.error(e.printStackTrace()) 
            status.setRollbackOnly()
        } 

        if(status.isRollbackOnly()){
            user.discard()
            user.refresh()
            loadObject(user)
            return render(view: 'edit', model:[user:user, person:person], params: params)
        }       
    }
}

加载所有类型为 domainClass 的属性的方法如下所示:

void loadObject(Object object, List<Object> loadedObjects = []){
    loadedObjects.add(object)
    for(MapEntry propertyEntryMap in object.properties){
        Object property = propertyEntryMap?.value
        if(grailsApplication.isDomainClass(property.getClass())){
            if(!loadedObjects.contains(property))
                loadObject(property, loadedObject)
        }
    }
    if(object.hasProperty("hasMany")){  
        for(def manyProperty in object.hasMany){
            for(Object property in object."${manyProperty.key}"){
                if(grailsApplication.isDomainClass(property.getClass())){
                   if(!loadedObjects.contains(property))
                       loadObject(property, loadedObject)
                }
            }
        } 
    }
}

你能得到的唯一问题是 hasMany 关系可能太大,这会减慢它的速度。

为了解决这个问题,我向 DomainClass 添加了一个属性,其中包含应该加载的 hasMany 关系:

static lazyProperties = ['addresses']

此属性会影响loadObject 方法,该方法将跳过该列表中的属性:

void loadObject(Object object, List<Object> loadedObjects = []){
    loadedObjects.add(object)
    for(MapEntry propertyEntryMap in object.properties){
        Object property = propertyEntryMap?.value
        if(object.hasProperty('lazyProperties')){ // check if object contains the property 'lazyProperties'
            if(object?.lazyProperties?.contains(propertyEntryMap.key))
                continue
        }
        if(grailsApplication.isDomainClass(property.getClass())){
            if(!loadedObjects.contains(property))
                loadObject(property, loadedObject)
        }
    }
    if(object.hasProperty("hasMany")){  
        for(def manyProperty in object.hasMany){
            if(object.hasProperty('lazyProperties')){ // check if object contains the property 'lazyProperties'
                 if(object?.lazyProperties?.contains(manyProperty.key))
                     continue
            }
            for(Object property in object."${manyProperty.key}"){
                if(grailsApplication.isDomainClass(property.getClass())){
                   if(!loadedObjects.contains(property))
                       loadObject(property, loadedObject)
                }
            }
        } 
    }
}

【讨论】:

    【解决方案2】:

    尝试扩展上述评论。

    错误表明出现严重问题,因此休眠必须关闭会话。

    考虑这个控制器:

     def doSomething() {
    
            SomeBean bean = new SomeBean(params.long('id'))
            bean.updateUser=userService.currentUser
            try {
                serice.foSomething(bean)
            } catch (ValidationException ve) {
                flash.message=message(error:ve.errors.getGlobalError())?:ve.errors
            } catch (DataIntegrityViolationException e) {
            } catch (HibernateObjectRetrievalFailureException e) {
    
            } catch (Throwable t) {
                flash.message= t.toString()
            }
            redirect(controller:'someController', action:'list')
        }
    

    它有如此多的尝试捕获的原因是因为我发现例如我得到了HibernateObjectRetrievalFailureException 的一段争论的代码,但实际工作已经完成 - 这使得你概​​述的那种 secnario 发生 - 或可能发生

    我的意思是尝试在服务方法周围使用 try catch 并首先弄清楚到底发生了什么。

    同样最重要的是,前 3 个尝试捕获实际上告诉控制器继续 - 只需忽略这些问题(我认为在这种情况下它们都会这样做)但如果它不是重定向,您可以在 throwable 中添加一些子句或重定向细分,所以你去其他地方或者简单地忽略它

    要完成以上所有,this may help you 解决您的问题

    import grails.validation.ValidationException
    class AuthorService {
    
        void updateAge(id, int age) {
            def author = Author.get(id)
            author.age = age
            if (!author.validate()) {
                throw new ValidationException("Author is not valid", author.errors)
            }
        }
    }
    

    基本上,当您最终捕获问题并找出导致问题的原因时,您可以在问题发生之前捕获它并返回您自己的自定义验证异常。

    这将被你的标准失败所接受,其他一切都会顺利进行。

    你的层可以验证并确保一切正常 - 你显示的当前验证层 - 问题并不真正在这里 - 当你要求 Hibernate 并去做一些无效的事情时 - 删除一些可以' t 被删除 - 做一些它不能做的事情 - 然后破坏它并导致你目前的头痛 - 所以通过在它发生之前捕获它们并执行上述操作将是你解决此类问题的方法

    【讨论】:

    • 一切都是为了预测某些类型的问题并处理它们——这就是为什么我们中的一些人会歪脖子:) 整天坐在那里
    • 感谢您的建议。基本上可行,但提交给更新操作的更改可能会丢失。 f.e.名字的输入具有默认值user.person.firstname,它将是存储在数据库中的旧值。
    猜你喜欢
    • 1970-01-01
    • 2017-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-16
    • 1970-01-01
    相关资源
    最近更新 更多