Search code examples
hibernategrailsgrails-domain-classgrails-controller

Grails Domain Create Association Table


I have a question about creating an associative table, in grails, to reconcile a many-to-many relationship. The setup is this: 1. Domain A (Client Profile) can have many Domain B (Friends) 2. Each Domain B (Friends) can have many Domain A (Client Profile) 3. To Resolve this, I need to create an Associative table (or domain) that has FK's from each table. This domain can be named Domain C (client_friend)

Here is the code I have so far:

class DomainA{
    String id
String firstName
String lastName
    static hasMany = [domainB: DomainB]
    static mapping = {
    cache true
    id generator: 'assigned'

    columns {
        firstName   type:'text'
        lastName    type:'text'
        alumConnections column: 'domaina_id', joinTable: 'a_b'
    }

}
static constraints = {
    firstName   (nullable:true)
    lastName    (nullable:true)
}

  }

DomainB Code:

   class DomainB{   
String id
String firstName
String lastName

    static hasMany = [domainA:DomainA]
static belongsTo = DomainA
static mapping = {
    cache true
    id generator: 'assigned'        

             columns {                  
        firstName       type:'text'
        lastName        type:'text'
        domainA column: 'domainb_id', joinTable: 'a_b'
    }
}
static constraints = {  
    firstName       (nullable:true)
    lastName        (nullable:true)

}
  }

Domain A_B code:

 class AB{


Date dateCreated
Date lastUpdated
long version

  }

When I run this code, it seems to work. The tables, using MySQL, are created, FK seem to be in place. When I enter data into the DomainB class, data is entered and both PK's from DomainA and DomainB are inserted into A_B. But, the problems comes when I try to delete values from A_B. I've tried something like this:

     AB results =AB.findByAIdAndBId('jIi-hRi4cI','2BYvuA2X14') 

but get an error: InvalidPropertyException: No property found for name [a_id] for class [class mgr.AB]

My question is this: first, have i set this up correctly? Second, if so, how then do i query the AB table who's PK is made up of a composite of DomainA and DomainB?

Thanks for any help.

jason


Solution

  • Your composite class isn't entirely correct. Look at this example and adjust yours accordingly. This is how I do all my composite domains:

    class UserRole implements Serializable {
    
        User user
        Role role
    
        boolean equals(other) {
            if (!(other instanceof UserRole)) {
                return false
            }
    
            other.user?.id == user?.id &&
                other.role?.id == role?.id
        }
    
        int hashCode() {
            def builder = new HashCodeBuilder()
            if (user) builder.append(user.id)
            if (role) builder.append(role.id)
            builder.toHashCode()
        }
    
        static UserRole get(long userId, long roleId) {
            find 'from UserRole where user.id=:userId and role.id=:roleId',
                [userId: userId, roleId: roleId]
        }
    
        static UserRole create(User user, Role role, boolean flush = false) {
            new UserRole(user: user, role: role).save(flush: flush, insert: true)
        }
    
        static boolean remove(User user, Role role, boolean flush = false) {
            UserRole instance = UserRole.findByUserAndRole(user, role)
            instance ? instance.delete(flush: flush) : false
        }
    
        static void removeAll(User user) {
            executeUpdate 'DELETE FROM UserRole WHERE user=:user', [user: user]
        }
    
        static void removeAll(Role role) {
            executeUpdate 'DELETE FROM UserRole WHERE role=:role', [role: role]
        }
    
        static mapping = {
            id composite: ['role', 'user']
            version false
        }
    }
    

    Once you have that, there is no need for the belongsTo and hasMany associations. But to access the data from those domains, you can provide methods like the following:

    class User {
    
       // typical domain junk
    
       Set<Role> getAuthorities() {
         UserRole.findAllByUser(this).collect { it.role } as Set
       }
    }
    

    Then you can do things like userInstance.authorites just as if the hasMany was there. Basically, you're doing what Grails would typical do for you. But this is actually a good thing. Collections in Grails can be costly if not done right. This is being addressed in 2.0 with the use of Bags.