Dynamic finders with Grails many-to-many relationship

大憨熊 提交于 2019-12-19 10:15:53

问题


I have 2 domain classes which are mapped by many-to-many relationship. I followed the instruction of Grails documentation, but I still have some problem when processing data on those domains. Here are my 2 domain classes:

class User {
    String name
    int age
    String job
    static hasMany = [groups : Group]
    static belongsTo = [org : Organization]
}

class Group {
    String groupName
    String code
    static hasMany = [members : User]
}

My problems are:
1. The above relationship require one class hold belongsTo to be the "owner" of the relationship. In this context, the User belongs to the Group, but I do not know how to put the belongsTo to User class, because the standard syntax that Grails suggest is static belongsTo = [Group] (just specify the owner class name), so I cannot:
- put it into the exist belongsTo like this: static belongsTo = [org : Organization, Group]
- or define another belongsTo like this: static belongsTo = [Group]

  1. Is below example right:

    class Book { String title static belongsTo = Author static hasMany = [authors:Author]

    static mapping = {
        authors joinTable:[name:"mm_author_books", key:'mm_book_id' ]
    }
    

    } class Author { String name static hasMany = [books:Book]

    static mapping = {
        books joinTable:[name:"mm_author_books", key:'mm_author_id']
    }
    

    }

(Ref link: Many-to-Many link tables in grails (GORM) / hibernate)
I mean that do we need to specify the name of foreign key of the join table for each class?

  1. If I want to find all User that are members of a specified Group whose name is "ABC", how can I use the DynamicFinder of Grails?

Thank you so much


回答1:


It is very rare that m2m relationships have an owning side, so I've always found it odd to have to specify one for GORM to work correctly. Because of this, I don't do it this way. I create the join table as a domain. Then things get really simple.

class UserGroup implements Serializable {

    User user
    Group group

    boolean equals(other) {
        if (!(other instanceof UserGroup)) {
            return false
        }

        other.user?.id == user?.id &&
            other.group?.id == group?.id
    }

    int hashCode() {
        def builder = new HashCodeBuilder()
        if (user) builder.append(user.id)
        if (group) builder.append(group.id)
        builder.toHashCode()
    }

    static UserGroup get(long userId, long groupId) {
        find 'from UserGroup where user.id=:userId and group.id=:groupId',
            [userId: userId, groupId: groupId]
    }

    static UserGroup create(User user, Group group, boolean flush = false) {
        new UserGroup(user: user, group: group).save(flush: flush, insert: true)
    }

    static boolean remove(User user, Group group, boolean flush = false) {
        UserGroup instance = UserGroup.findByUserAndGroup(user, group)
        instance ? instance.delete(flush: flush) : false
    }

    static void removeAll(User user) {
        executeUpdate 'DELETE FROM UserGroup WHERE user=:user', [user: user]
    }

    static void removeAll(Group group) {
        executeUpdate 'DELETE FROM UserGroup WHERE group=:group', [group: group]
    }

    static mapping = {
        id composite: ['group', 'user']
        version false
    }
}

Then you just need to create the getters in your User and Group class. You won't have User user or Group group in either class. There is no need to map them with hasMany/belongsTo because all that will do is create the join table, which you've done by creating the UserGroup domain.

class User {
   Set<Group> getGroups() {
    UserGroup.findAllByUser(this).collect { it.group } as Set
  }
}

class Group {
  Set<User> getUsers() {
    UserGroup.findAllByGroup(this).collect { it.user } as Set
  }
}

Once you have these in place you can use the methods you created in the UserGroup domain and/or you can use finders on it...

def userGroupInstance = UserGroup.findByUserAndGroup(userInstance, groupInstance)
def userGroups = UserGroup.findAllByUser(userInstance)
def userGroupInstance = UserGroup.get(userId, groupId)

You get the idea. This presentation by Burt Beckwith sheds more light on why this is a good approach along with some other great tips for performance increases.




回答2:


  1. belongsTo will create another M:1 relationship (field) - you tell if there is a "main group" for a User in your case. If I wished only to enforce at least one group, I'd go with a custom validator for User.groups checking it's not empty.

  2. (I'm not sure here) - I believe yes, if the key name differs from default Hibernat/GORM "userId"/"groupId".

  3. findBy*() methods won't work here, you need a CriteriaBuilder, like here.



来源:https://stackoverflow.com/questions/6545637/dynamic-finders-with-grails-many-to-many-relationship

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!