Grails Gorm : Object references an unsaved transient instance
Asked Answered
C

8

5

I get the following Exception when saving an instance of Trip in Grails:

2011-01-26 22:37:42,801 [http-8090-5] ERROR errors.GrailsExceptionResolver - object references an unsaved transient instance - save the transient instance before flushing: Rower org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: Rower

The concept is simple: For a boattrip you need some rowers, a coxwain (is also a rower) and a boat:

Trip looks like (shortened):

class Trip {
    Boat boat;
    Rower coxwain;

    static belongsTo = [Rower,Boat]
    static hasMany = [rowers:Rower]
}

and Rower (shortened)

class Rower { 
    String firstname;
    String name;
    Rower reference;

    static hasMany = [trips:Trip];
    static mappedBy = [trips:"rowers"]
}

The trip then is saved in the controller like:

def save = {
        def trip = new Trip(params)

        // adding Rowers to Trip
        if (params.rower instanceof String) {
            def r = Rower.get(params?.rower)

            if (r != null) {
                trip.addToRowers(r)
            }
        } else {
            params?.rower?.each{
                rowerid ->
                def r = Rower.get(rowerid)
                log.info("rowerid (asList): " + rowerid)
                if (r != null) {
                    trip.addToRowers(r)
                }
            }
        } 

        // saving the new Trip -> EXCEPTION IN NEXT LINE
        if(!trip.hasErrors() && trip.save(flush:true)) {
          // ...
        }
        // ...
}

I think I have set the relations between the domains correct. The Rower is not changed while it is added to the Trip. Why does Grails want it to save? why is it a transient instance?

Contrapuntist answered 26/1, 2011 at 22:4 Comment(2)
Does it happen in both controller blocks (i.e. the if and the else)?Gt
Did you get any logging message?Cicisbeo
I
7

Unfortunately this is an issue with the way GORM handles things, or more specifically the way that it expects that you deal with transients. If you don't persist the contained classes to the database first (Rowers in this case), you will get this exception every single time.

With GORM you have to save and attach in a bottom up fashion or when grails goes to flush the connection for the next instance you will get the transient instance exception. The instance is 'transient' because its just an in-memory reference. To persist the parent, GORM needs to link the parent to the child in the database. Without the child being persisted it has no way to do that, this is where the exception is coming from.

Wish there was better news. Not that its hard, but it gets annoying with complex hierarchies.

Interjoin answered 13/2, 2011 at 4:31 Comment(0)
C
1

The Problem was somehow different. it's in here:

def trip = new Trip(params)

which references a coxwain (of class Rower), which is not set (id=-1 is returned). This constructs a new Rower instead of a 'null' value. And this is the 'unsaved transient instance'. If I check first for a valid instance, then it works :-)

Thanks for the help!

Contrapuntist answered 28/1, 2011 at 22:31 Comment(0)
A
1

Just a quick note for anyone dealing with singular or multiple parameters with the same name, using the params.list("parameterName") helper you can always return a list

    ...

    // adding Rowers to Trip
    if (params.rower instanceof String) {
        def r = Rower.get(params?.rower)

        if (r != null) {
            trip.addToRowers(r)
        }
    } else {
        params?.rower?.each{
            rowerid ->
            def r = Rower.get(rowerid)
            log.info("rowerid (asList): " + rowerid)
            if (r != null) {
                trip.addToRowers(r)
            }
        }
    } 

    ...

could become a bit groovier

    ...

    // adding Rowers to Trip
    for(rower in params.list("rower") {
        def r = Rower.get(rower) 
        if(r) trip.addToRowers(r)
    } 

    ...

you can find it hiding away under 6.1.12 Simple Type Converters

Antic answered 7/7, 2011 at 18:34 Comment(0)
H
0

I think you have to save the trip before you add the rover to the trip. Also it make no sense to check if trip has errors before you validate and/or save it.

Try this:

if(trip.validate() && trip.save(flush:true)) {
    if (r != null) {
       trip.addToRowers(r)
    }  
}
Horner answered 27/1, 2011 at 9:30 Comment(0)
I
0

At first I thought it was to do with cascading saves and belongsTo, as described in The Grails Reference section 5.2.1.3 and Gorm Gotchas part 2. However since the Rowers are already in the DB I think it should work. The domain model is complicated to me, what you need to do is simplify it and run some tests using Grails console (run grails console in your project directory). First create a basic many-to-many between Trip and Rower and get it to execute the desired code. Then add the other parts bit-by-bit, like Rower's reference to itself. I'm not sure that the mappedBy part is necessary at all.

Ignatzia answered 27/1, 2011 at 9:54 Comment(0)
G
0

In many use cases, you should be able to address this by applying the cascade setting to your collection:

static mapping = {
    rowers cascade: 'all-delete-orphan'
}

https://docs.grails.org/latest/ref/Database%20Mapping/cascade.html

Gunderson answered 28/9, 2017 at 21:25 Comment(0)
H
0

Suppose, you are using Trip is hasMany relationship with Rower

class Trip {
      static hasMany = [rowers:Rower]
      ...
    }

class Rower{
      static belongsTo =[trips:Trip]
      ...
   }

Actually, it's looking for new session. So we need to stop/rollback the current transaction then this error won't raise, For that Try This,

  def row = new Row()
  row.save()

NOTE: this save won't affect your database. It's just for rollback the transaction.

Hl answered 14/6, 2018 at 6:15 Comment(0)
S
0

I had this problem but the answer for me was much simpler - my first object had not saved properly because it didn't validate successfully. I somehow didn't notice that the first object had not saved, and so the error was confusing to me. While debugging the problem, I eventually noticed that the first object was not saving properly, and when I fixed that, the object references an unsaved transient instance problem went away.

Sophy answered 13/11, 2021 at 14:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.