Grails JIRA

  • Log In Access more options
    • Online Help
    • GreenHopper Help
    • Agile Answers
    • Keyboard Shortcuts
    • About JIRA
    • JIRA Credits
    • What’s New
  • Dashboards Access more options (Alt+d)
  • Projects Access more options (Alt+p)
  • Issues Access more options (Alt+i)
  • Agile
Grails
  • Grails
  • GRAILS-3396 Top level task: GORM Improvements
  • GRAILS-2480

Evict object from Hibernate in update Methods.

  • Log In
  • Views
    • XML
    • Word
    • Printable

Details

  • Type: Sub-task Sub-task
  • Status: Closed Closed
  • Priority: Major Major
  • Resolution: Fixed
  • Affects Version/s: 1.0
  • Fix Version/s: 1.1-beta1
  • Component/s: Controllers
  • Labels:
    None

Description

Currently the symantics of Hibernate are such that changes to the db are automatically performed and committed if an loaded Object is modified. So the User Object changes to the Database are performed when groovy execute "u.properties = params". So the Object is modified, but u.save() was never called.

Seems that the loaded Object is bound to the Hibernate Session, so Hibernate checks the Object for changes.

Unable to find source-code formatter for language: groovy. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
def u = User.get(1)
u.properties = params
// validation fails here

// Everything below is senseless for the "u" Object, because Changes are already written to the Database
if(u.save()) {

}
else {
  // handle errors
}

I think this is very critical, because the Grails validation won't work correct.
So a validation Error is displayed, but the Object has been modified ?!

Is there a method to evict the Object from the Hibernate Session? (u.evict() doesn't work).

Activity

Ascending order - Click to sort in descending order
  • All
  • Comments
  • Work Log
  • History
  • Activity
  • Git Commits
Hide
Permalink
Graeme Rocher added a comment - 18/Feb/08 7:44 AM

You can use discard(). We used to evict, but it causes problems as the associations might still be in the session, now we set the session flush mode to manual on a validation error. So the changes don't get flushed to the db on a validation error, you can still force them to be using save(flush:true) to recover from a validation error programmatically, but as far as I'm concerned this issue is not an issue

Show
Graeme Rocher added a comment - 18/Feb/08 7:44 AM You can use discard(). We used to evict, but it causes problems as the associations might still be in the session, now we set the session flush mode to manual on a validation error. So the changes don't get flushed to the db on a validation error, you can still force them to be using save(flush:true) to recover from a validation error programmatically, but as far as I'm concerned this issue is not an issue
Hide
Permalink
Graeme Rocher added a comment - 18/Feb/08 7:45 AM

Just to be clear validation executes as part of the called to save(), if save() fails to validate the flush mode is set to manual, hence you should always call save() when doing updates

Show
Graeme Rocher added a comment - 18/Feb/08 7:45 AM Just to be clear validation executes as part of the called to save(), if save() fails to validate the flush mode is set to manual, hence you should always call save() when doing updates
Hide
Permalink
Martin Kremers added a comment - 18/Feb/08 9:26 AM

I found a solution for the problem in the Mailing list by Graeme himself

page.discard() does it.

But the discard() method isn't used by the generate-all (scaffolding) command? I can't find it in the templates!?

Show
Martin Kremers added a comment - 18/Feb/08 9:26 AM I found a solution for the problem in the Mailing list by Graeme himself page.discard() does it. But the discard() method isn't used by the generate-all (scaffolding) command? I can't find it in the templates!?
Hide
Permalink
Graeme Rocher added a comment - 18/Feb/08 9:31 AM

Its not needed, because we call save() in the templates if a validation error occurs the flush mode will be set to manual preventing the changes from being flushed

Show
Graeme Rocher added a comment - 18/Feb/08 9:31 AM Its not needed, because we call save() in the templates if a validation error occurs the flush mode will be set to manual preventing the changes from being flushed
Hide
Permalink
Martin Kremers added a comment - 18/Feb/08 9:36 AM

I tryed it with:

if (!u.hasErrors() && u.save()) {
                    request.message = "User ${params.id} updated"
                    redirect(action: 'index')
...

but "u.properties = params" has already changed my database values!!

So it's senseless to set the flush mode to manual, because the "flush" has been already performed.

I've done it now with the ".discard()". But if the Hibernate session is flushed when I modify my properties values, how can it be transaction save (lost update anormalies etc.)?

Show
Martin Kremers added a comment - 18/Feb/08 9:36 AM I tryed it with: if (!u.hasErrors() && u.save()) { request.message = "User ${params.id} updated" redirect(action: 'index') ... but "u.properties = params" has already changed my database values!! So it's senseless to set the flush mode to manual, because the "flush" has been already performed. I've done it now with the ".discard()". But if the Hibernate session is flushed when I modify my properties values, how can it be transaction save (lost update anormalies etc.)?
Hide
Permalink
Graeme Rocher added a comment - 18/Feb/08 9:48 AM

hmm this shouldn't happen, please post a sample application with steps to reproduce, in the meantime you have the dicard() workaround

Show
Graeme Rocher added a comment - 18/Feb/08 9:48 AM hmm this shouldn't happen, please post a sample application with steps to reproduce, in the meantime you have the dicard() workaround
Hide
Permalink
Martin Kremers added a comment - 18/Feb/08 9:59 AM

Ok, i'll create a sample application.

So you should give the Issue a lower priority?

Show
Martin Kremers added a comment - 18/Feb/08 9:59 AM Ok, i'll create a sample application. So you should give the Issue a lower priority?
Hide
Permalink
Graeme Rocher added a comment - 18/Feb/08 10:06 AM

i think we should try and find a solution for 1.0.2 or 1.1 but i wouldn't say its a blocker, in my tests the changes were not being flushed on validation error so its seems a particular set of circumstances that is causing your changes to be flushed. Also you have a workaround

Show
Graeme Rocher added a comment - 18/Feb/08 10:06 AM i think we should try and find a solution for 1.0.2 or 1.1 but i wouldn't say its a blocker, in my tests the changes were not being flushed on validation error so its seems a particular set of circumstances that is causing your changes to be flushed. Also you have a workaround
Hide
Permalink
Caine added a comment - 04/Apr/08 12:54 AM

Well the problem with the workaround is that by calling discard() on the object, you will get a lazyinitialization exception in the view if your view needs access to collections on the object being updated.

Some sample code from my grails console to illustrate:

def clientService = new com.myCompany.service.ClientService()
def client = clientService.getClientByName("Test")
client.name = "Test2" // This name is already taken, violating a unique constraint
clientService.updateClient(client) // Service calls validate and returns on failure, setting errors into object
if (! client.hasErrors()) {
// This block doesn't get reached for this example do to the validate failing in the service
} else {
// We have to discard or we get exception do
// to hibernate saving the changes we made to object
client.discard()
// Forward to page
client.errors.each

{ println it }

// But this fails after discard since jobs is a collection that has not been initialized yet. It's needed in the page.
println client.jobs
}
return client

By the way, "we call save() in the templates if a validation error occurs the flush mode will be set to manual preventing the changes from being flushed". This statement is only true if being called directly in the controller. But calls to save() in a service to not set flush mode to manual as my tests confirm.

Show
Caine added a comment - 04/Apr/08 12:54 AM Well the problem with the workaround is that by calling discard() on the object, you will get a lazyinitialization exception in the view if your view needs access to collections on the object being updated. Some sample code from my grails console to illustrate: def clientService = new com.myCompany.service.ClientService() def client = clientService.getClientByName("Test") client.name = "Test2" // This name is already taken, violating a unique constraint clientService.updateClient(client) // Service calls validate and returns on failure, setting errors into object if (! client.hasErrors()) { // This block doesn't get reached for this example do to the validate failing in the service } else { // We have to discard or we get exception do // to hibernate saving the changes we made to object client.discard() // Forward to page client.errors.each { println it } // But this fails after discard since jobs is a collection that has not been initialized yet. It's needed in the page. println client.jobs } return client By the way, "we call save() in the templates if a validation error occurs the flush mode will be set to manual preventing the changes from being flushed". This statement is only true if being called directly in the controller. But calls to save() in a service to not set flush mode to manual as my tests confirm.
Hide
Permalink
Graeme Rocher added a comment - 04/Apr/08 9:39 AM

Currently in the case of a service if you get a validation error you should throw a runtime exception which will rolllback the transaction

Show
Graeme Rocher added a comment - 04/Apr/08 9:39 AM Currently in the case of a service if you get a validation error you should throw a runtime exception which will rolllback the transaction
Hide
Permalink
Marc Palmer added a comment - 17/Apr/08 5:53 AM

Not sure if this is related but with 1.0.2 scaffolding when we delete we are getting:

Caused by: org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value: Stockist.type; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value: Stockist.type
at StockistController$_closure4.doCall(StockistController.groovy:27)
at StockistController$_closure4.doCall(StockistController.groovy)

The line of code is the stockist.delete()

    def delete = {
        def stockist = Stockist.get( params.id )
        if(stockist) {
            stockist.delete()
            flash.message = "Stockist ${params.id} deleted"
            redirect(action:list)
        }
        else {
            flash.message = "Stockist not found with id ${params.id}"
            redirect(action:list)
        }
    }

The object being deleted has a "type" property that is null in the db due to it being old data before the property existed, and the Stockist class now has this property with a default value assigned to it:

class Stockist {
    String name
    String nameOrNumber
    String addressLine1
    String addressLine2
    String town
    String county
    String postCode
    String type = Schema.STOCKISTTYPE_PUB
    
    static mapping = {
        cache usage:"nonstrict-read-write"
    }
    static constraints = {
        name(size:1..60, blank: false, nullable: false)
        nameOrNumber(size:0..60, blank: true, nullable: true)
        addressLine1(size:1..60, blank: false, nullable: false)
        addressLine2(size:0..60, blank: true, nullable: true)
        town(size:1..60, blank: false, nullable: false)
        county(size:0..60, blank: true, nullable: true)
        postCode(size:1..10, blank: false, nullable: false)
        type(size:1..20, blank:false, nullable: false, inList:Schema.STOCKISTTYPES)
    }
}

So the issue here is that it seems to be trying to SAVE when we call delete() which surely should not be happening, and makes me think this is related?

Show
Marc Palmer added a comment - 17/Apr/08 5:53 AM Not sure if this is related but with 1.0.2 scaffolding when we delete we are getting: Caused by: org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value: Stockist.type; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value: Stockist.type at StockistController$_closure4.doCall(StockistController.groovy:27) at StockistController$_closure4.doCall(StockistController.groovy) The line of code is the stockist.delete() def delete = { def stockist = Stockist.get( params.id ) if (stockist) { stockist.delete() flash.message = "Stockist ${params.id} deleted" redirect(action:list) } else { flash.message = "Stockist not found with id ${params.id}" redirect(action:list) } } The object being deleted has a "type" property that is null in the db due to it being old data before the property existed, and the Stockist class now has this property with a default value assigned to it: class Stockist { String name String nameOrNumber String addressLine1 String addressLine2 String town String county String postCode String type = Schema.STOCKISTTYPE_PUB static mapping = { cache usage: "nonstrict-read-write" } static constraints = { name(size:1..60, blank: false , nullable: false ) nameOrNumber(size:0..60, blank: true , nullable: true ) addressLine1(size:1..60, blank: false , nullable: false ) addressLine2(size:0..60, blank: true , nullable: true ) town(size:1..60, blank: false , nullable: false ) county(size:0..60, blank: true , nullable: true ) postCode(size:1..10, blank: false , nullable: false ) type(size:1..20, blank: false , nullable: false , inList:Schema.STOCKISTTYPES) } } So the issue here is that it seems to be trying to SAVE when we call delete() which surely should not be happening, and makes me think this is related?
Hide
Permalink
Peter Ledbrook added a comment - 12/Aug/08 3:00 AM

Hi Marc,

The delete() method simply calls session.delete(), so I think this is down to Hibernate. Just found a hibernate bug report that looks relevant to the behaviour you're seeing.

Show
Peter Ledbrook added a comment - 12/Aug/08 3:00 AM Hi Marc, The delete() method simply calls session.delete() , so I think this is down to Hibernate. Just found a hibernate bug report that looks relevant to the behaviour you're seeing.
Hide
Permalink
Peter Ledbrook added a comment - 12/Aug/08 3:05 AM

I'm going to send a mail to the dev list about this issue, but I think we should move it to 1.1 since it will almost certainly require a fairly significant change. We may switch to using the readOnly property on Hibernate domain instances, but we need to think on exactly how things should behave.

Show
Peter Ledbrook added a comment - 12/Aug/08 3:05 AM I'm going to send a mail to the dev list about this issue, but I think we should move it to 1.1 since it will almost certainly require a fairly significant change. We may switch to using the readOnly property on Hibernate domain instances, but we need to think on exactly how things should behave.
Hide
Permalink
Graeme Rocher added a comment - 28/Oct/08 6:37 AM

Grails not sets the object to read-only if a validation error occurs or a data binding error occurs, eliminating the lazy load problems that result from using discard()

Show
Graeme Rocher added a comment - 28/Oct/08 6:37 AM Grails not sets the object to read-only if a validation error occurs or a data binding error occurs, eliminating the lazy load problems that result from using discard()
Hide
Permalink
Caine added a comment - 28/Oct/08 11:29 AM

This is excellent news. Thank you very much for this.

Show
Caine added a comment - 28/Oct/08 11:29 AM This is excellent news. Thank you very much for this.

People

  • Assignee:
    Graeme Rocher
    Reporter:
    Martin Kremers
Vote (10)
Watch (5)

Dates

  • Created:
    18/Feb/08 6:43 AM
    Updated:
    28/Oct/08 11:29 AM
    Resolved:
    28/Oct/08 6:37 AM

Agile

  • View on Board
  • Atlassian JIRA (v5.2.1#813-sha1:277a546)
  • Report a problem
  • Powered by a free Atlassian JIRA open source license for Grails project. Try JIRA - bug tracking software for your team.