Search code examples
grailsgrails-orm

hasMany save not working on Grails 2.4.2 upgrade


I recently upgraded from Grails 2.2.5 to 2.4.2 . After the upgrade a lot of my hasMany relationships are not saving.

For Example:

Domains:
    class Node {
        String name
        String description

        static belongsTo = CustomGlobe
        static hasMany = [containers: Container]
    }

    class Container {
        String name
        CustomGlobe customGlobe

        static belongsTo = Node
        static hasMany = [nodes: Node]
    }

    class CustomGlobe {
        String name

        static belongsTo = CustomLocation
        static hasMany = [customLocations: CustomLocation, nodes: Node]
    }

    class CustomLocation {
        String name
        String description
    }

On the service that performs the transaction I did add a @Transactional above the class def. I also attempted adding a @Transactional(propagation=Propagation.REQUIRES_NEW) per Grails 2.4.2: Strange cascading save behaviour in nested transactions . If I rollback the Grails upgrade (same controller, service, and view code) the nodes set is persisted correctly, however, with Grails 2.4.2 it does not. I also checked right before and right after the save by printing the object's nodes and it displays on the console, but when my application redirects to the list view it does not show and is not persisted anywhere.

--UPDATE-- This still occurs in Grails 2.4.3 I also believe it may have something to do with the join table, but I can't figure out why. The Container has nodes attached to it after params binding, but after the .save() it is not persisted to the join table.

--UPDATE-- Sorry, there was an error in the post of the Domain class code, it has been updated and is correct now. Hopefully someone can shed some light on what I'm missing now.

The issue occurs with the Nodes collection not being persisted to the Container instance in the NODE_CONTAINERS table.

--UPDATE-- Issue is ongoing. When debugging I created a new groovy Sql instance with the Grails datasource and manually inserted the Nodes into the NODE_CONTAINERS table. Everything saved correctly and was recalled correctly when viewing the Container show gsp. So it appears that GORM is treating the join table correctly when reading the instance, still not sure why it isn't saving the Nodes to the join table correctly.

SAMPLE APPLICATIONS FOR DEMONSTRATION OF ERROR:

  1. Working Application using Grails 2.2.5 (2.2.5 branch)
  2. Application exhibiting error described above using Grails 2.4.3 (MASTER branch)

(https://github.com/bwagner5/grailsCollectionsDebugApp/tree/master)

Grails Data Binder:

The issue seems to be the Grails Data Binder. The Spring Data Binder works fine (default in 2.2.x and you are able to override the Grails binder in 2.3.x but not 2.4.x) I have put in a JIRA but would still like to see if there is a workaround for now: https://jira.grails.org/browse/GRAILS-11638


Solution

  • I would recommend actually adding separate join classes, which is also a recommendation made by Burt Beckwith and you'll find this practice used in the Grails Spring Security Core project. For example with just your Node and Container classes you'd end up with:

    class Node {
        String name
        String description
        Set<NodeContainer> nodeContainers = []
        static hasMany = [nodeContainers: NodeContainer]
    }
    
    class Container {
        String name
        CustomGlobe customGlobe
    
        //potentially add a helper method for fetching the nodes, but no GORM specification should be made on this class
    }
    
    
    class NodeContainer {
        Container container
        Node node
    
        static NodeContainer addNodeContainer(Node node, Container container){
            def n = findByNodeAndContainer(node, container)
            if(!n){
                n = new NodeContainer(container: container)
                node.addToContainers(n)
                n.save()
            }  
        }
    
        // Other helper methods such as remove and table mappings if necessary
    }
    

    In this way, you are creating and removing the joins one at a time instead of requiring hibernate to load the whole Set and then add/remove items from it. You are also removing the need to have hasMany on both classes, which can have performance and save issues on large sets. I've managed to resolve all of my funky saving issues by implementing this pattern. Good luck and I hope this helps!