0
votes

I'm trying to create a bi-directional relationship between two domains in Grails (parent-child) but i can't seem to make it work. According to Grails GORM documentation oneToMany I should be able to create a hasMany(Parent) and a belongsTo(Child) between a parent and a child to create a bi-directional relationship but it just doesn't work for me.

I have the following two domains:

class Game {
   String name
   String description
   Double price
}

class Review {
   static belongsTo = [game: Game]
   String reviewText
   Date reviewDate

}

then i create a grails dbm-gorm-diff file.groovy i get a file with the following

databaseChangeLog = {

 changeSet(author: "efx (generated)", id: "1456032538941-1") {
    createTable(tableName: "game") {
        column(name: "id", type: "int8") {
            constraints(nullable: "false", primaryKey: "true", primaryKeyName: "gamePK")
        }

        column(name: "version", type: "int8") {
            constraints(nullable: "false")
        }

        column(name: "description", type: "varchar(255)") {
            constraints(nullable: "false")
        }

        column(name: "name", type: "varchar(255)") {
            constraints(nullable: "false")
        }

        column(name: "price", type: "float8") {
            constraints(nullable: "false")
        }

        column(name: "reviews_id", type: "int8") {
            constraints(nullable: "false")
        }
    }
}

 changeSet(author: "efx (generated)", id: "1456032538941-2") {
    createTable(tableName: "review") {
        column(name: "id", type: "int8") {
            constraints(nullable: "false", primaryKey: "true", primaryKeyName: "reviewPK")
        }

        column(name: "version", type: "int8") {
            constraints(nullable: "false")
        }

        column(name: "review_date", type: "timestamp") {
            constraints(nullable: "false")
        }

        column(name: "review_text", type: "varchar(255)") {
            constraints(nullable: "false")
        }
    }
}

 changeSet(author: "efx (generated)", id: "1456032538941-4") {
    createSequence(sequenceName: "hibernate_sequence")
}

changeSet(author: "efx (generated)", id: "1456032538941-3") {
    addForeignKeyConstraint(baseColumnNames: "reviews_id", baseTableName: "game", constraintName: "FK_jnjkmccicsjmsvqub534xcnnm", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "review", referencesUniqueColumn: "false")
}

}

at this point everything is 'perfect' so to say so i run grials dbm-update so the changes get transferred to the DB, but then i want to make this relationship bi-directional therefore i update my Game domain with the 'hasMany' as follows

class Game {
   static hasMany = [reviews: Review]
   String name
   String description
   Double price
}

after making the changes to the Game domain i proceed to run grails dbm-gorm-diff fileupdated.groovy so i can finally create the bi-directional relationship but i get an empty migration file

databaseChangeLog = {
}

Note: even if put the 'hasMany" in the first migration on the Game domain I receive the same results, a relationship child-parent(reviews to game) is created, but the parent-child(game to reviews) doesn't get created. On the tutorial i was trying to follow it does work. I'm using grails 2.4.4.

Why is the oneToMany relationship not being created?

thank you,

efx

Edit:

-I create a Game example like below and receive id = 1

    groovy:000> g = new com.pluralsight.Game([description: 'Game Desc', name: 'The Game', price: '1.99'])
===> com.pluralsight.Game : (unsaved)
groovy:000> g.save()
===> com.pluralsight.Game : 1

-Then i proceed to create 2 reviews for that game and receive review id 2 and 3

    groovy:000> r = new com.pluralsight.Review([game: g, reviewDate: new Date(), reviewText: 'Review 1'])
===> com.pluralsight.Review : (unsaved)
groovy:000> r.save()
===> com.pluralsight.Review : 2

groovy:000> r2 = new com.pluralsight.Review([game: g, reviewDate: new Date(), reviewText: 'Review 2'])
===> com.pluralsight.Review : (unsaved)
groovy:000> r2.save()
===> com.pluralsight.Review : 3

-now if everything was done correctly with my bidirectional domains i should be able to query all my reviews from game but i get a null

    groovy:000> retrieve = com.pluralsight.Game.get(1)
===> com.pluralsight.Game : 1
groovy:000> retrieve.reviews
===> null

therefore i'm not sure why my oneToMany from Game to Review is not working

3
You gotta save your reviews from the point of view of the game by adding it: g.addToReviews(new Review(review:date new Date(), reviewText: 'Review 1'). Then when you save the game the review will be saved as well: g.save()Emmanuel Rosa
@EmmanuelRosa i get an error which i'm going to guess is exactly the same where i get null when i try to retrieve the reviews from Game as my last piece of code above. groovy:000> retrieve.addToReviews(new Review(reviewDate: new Date(), reviewText: 'Review 3')) ERROR org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed: groovysh_evaluate: 2: unable to resolve class Review @ line 2, column 23. retrieve.addToReviews(new Review(reviewDate: new Date(), reviewText: 'Review 3'))efx
No, the problem is that in my example I simply didn't use a fully qualified class name. Try: g.addToReviews(new com.pluralsight.Review(review:date new Date(), reviewText: 'Review 1')Emmanuel Rosa
@EmmanuelRosa i get the following error groovy:000> retrieve.addToReviews(new com.pluralsight.Review(reviewDate: new Date(), reviewText: 'Review 3')) ERROR groovy.lang.MissingMethodException: No signature of method: com.pluralsight.Game.addToReviews() is applicable for argument types: (com.pluralsight.Review) values: [com.pluralsight.Review : (unsaved)] Possible solutions: getReviews() i don't think is ever going to work if there's no relationship from Game to Review. Right now the only relationship is from Review to Game but no vice versaefx
Yes, you are correct. You need an association from Game to Review. Use either one of the examples in my answer. The many is added to the one regardless of whether the association is uni-directional or bi-directional. BTW, feel free to sign up for my Grails Noob Slack community (at emmanuelrosa.com). The StackOverflow chat is going to kick in soon, and it sucks.Emmanuel Rosa

3 Answers

0
votes

Why do you expect the database to change?

What is important is to understand what Grails/GORM (and Hibernate) mean when talking about a bidirectional relationship.

A relationship is called bidirectional when both ends a the relationship have a reference to the other side. So the Game has a collection of Reviews and the Review has a reference to the Game. This is only talking about the application code. The database only needs 1 foreign key to fully store the relationship.

You could also use a join table, but that is really overkill in this case (in my opinion). But Grails/GORM allows it, just look in the docs how to map it in the static mapping section.

0
votes

As you know, there are two forms of one-to-many associations: uni-directional and bi-directional. Both require static hasMany = .... It's only in the case of a bi-directional one-to-many association that static belongsTo = ... is used:

Uni-directional

class Game {
   String name
   String description
   Double price

   static hasMany = [reviews: Review]
}

class Review {
   String reviewText
   Date reviewDate    
}

Bi-directional

class Game {
   String name
   String description
   Double price

   static hasMany = [reviews: Review]
}

class Review {
   String reviewText
   Date reviewDate

   static belongsTo = [game: Game]
}

Database implementation

In the case of a uni-directional one-to-many, you'll end up with three tables: game, review, and game_review. The game_review table joins the other two tables by using two columns: the game ID and the review ID. The game and review tables remain isolated; they only have a relationship through the game_review table.

In a bi-directional one-to-many, you'll get two tables: game and review. However, the review table will contain a foreign key column: the game ID. There's no join table keeping the game and review tables independent of each other.

I hope that helps. You can read more about this in one of my articles.

0
votes

I would like to thank @hansboogards and @emmanuelrosa for your time, i found a very interesting article about hibernate and the headaches it causes GORM Gotchas and found the reason i couldn't call from Game domain my reviews it was because hibernate haven't put them on the DB although i had done the save.

Even after a save() (without an explicit flush), this will print nulls. It’s only when you flush the session that the child domain instances have their IDs set. This is quite different to the many-to-one case we saw earlier, where you didn’t need an explicit flush for the Location instance to be persisted to the database! It’s important to realise that difference exists, otherwise you’ll be in for a hard time.