简体   繁体   中英

Grails inheritance and conflicting one-to-many and many-to-many relationship

I'm learning grails by trying to create a simple twitter copy. I'm currently trying to incorporate followers and groups. I originally came up with a very basic database structure, and I've had no luck in implementing it. The design for relationships is as follows:

    Person:
        has many: Groups, Tweets, (Person as followers through User2Person)
    Group:
        has many: (Person as followers through User2Person)
        belongs to: Person as owner
    User2Person:
        belongs to: (Person or Group)
        belongs to: Person

Basically, I want Person and Group to be an instance of User, and then create a table that maps User to Person. This way, only one table is created/used for the relationship between Group2Person and Person2Person.

More information: A Group is created by a Person and so it should have an "owner" (person_id). It also has many followers (ie members). Group cannot follow other groups, but a Person can follow either another Person or a Group.

Below is how I implemented this in grails:

User

    abstract class User {
        static hasMany = [followers: Person]
        static mappedBy = [followers: "followed"]
        String name
        Date dateCreated
        Date lastUpdated

        static constraints = {
            name shared: "mustFill", size: 3..20
        }
    }

Person

    class Person extends User {
        static belongsTo = [followed: User]
        static hasMany = [tweets: Tweet, groups: Group]
        static mappedBy = [groups: "owner"]
        String username
        String email

        static constraints = {
            username shared: "mustFill", unique: true, size: 4..15
            email shared: "mustFill", email: true
        }

        static mapping = {
            tweets sort: 'dateCreated', order: 'desc'
        }

    }

Group

    class Group extends User {
        Person owner
        String description

        def getTweets() {
            return followers.tweets.flatten()
        }

        static transients = {
            tweets
        }
    }

Tweet (Just in case?)

    class Tweet {
        static belongsTo = [author: Person]
        String text
        Date dateCreated

        static constraints = {
            text shared: "mustFill", maxSize: 140
        }
    }

When I run the cmd grails schema-export , I get the following error: "| Error Error loading plugin manager: Domain classes [class tweeter.Group] and [class tweeter.Person] cannot own each other in a many-to-many relationship. Both contain belongsTo definitions that reference each other. (Use --stacktrace to see the full trace)"

I was able to get the database to create almost the correct schema. Unfortunately, the join table's primary key for User2Person (aka followers) used (user_id, person_id). That meant that I could not have two records such as: (1, 2) and (2, 1) (eg two users are following each other). Below is the updated classes ( commit ):

User

    class User {
        static belongsTo = Person
        static hasMany = [followers: Person]
        String name
        Date dateCreated
        Date lastUpdated

        static constraints = {
            name shared: "mustFill", size: 3..20
        }
    }

Person

    class Person extends User {
        static hasMany = [tweets: Tweet, groups: Group, follows: User]
        static mappedBy = [tweets: "author", groups: "owner"]
        String username
        String email

        static constraints = {
            username shared: "mustFill", unique: true, size: 4..15
            email shared: "mustFill", email: true
        }

        static mapping = {
            tweets sort: 'dateCreated', order: 'desc'
        }

    }

The follower table in the schema looked like:

    create table user_follows (
        user_id bigint,
        follows__id bigint,
        primary_key(user_id, follows__id)
    )

I scoured the web for information about changing the primary key for a join table. The best I could find was about using code like:

    static mappedBy = { followers joinTable: [name:"someName", ...] }

Unfortunately, I had a hard time finding good documentation on the joinTable mapping, and most sources seemed to indicate that it was not possible to change the primary key of join tables easily. I then decided to use a separate domain class to define the join table following this guide: Many-to-Many Mapping without Hibernate XML. Below is the final updated code ( commit ):

User

    class User {
        static belongsTo = Person
        static hasMany = [people: UserFollower]
        static mappedBy = [people: "followed"]
        String name
        Date dateCreated
        Date lastUpdated

        static constraints = {
            name shared: "mustFill", size: 3..20
        }

        static transients = {
            followers
        }

        def getFollowers() {
            return people.collect { it.follower }
        }

        void addToFollowers(Person person) {
            UserFollower.link(this, person)
        }

        void removeFromFollowers(Person person) {
            UserFollower.unlink(this, person)
        }
    }

Person

    class Person extends User {
        static hasMany = [tweets: Tweet, groups: Group, users: UserFollower]
        static mappedBy = [tweets: "author", groups: "owner", users:"follower"]
        String username
        String email

        static constraints = {
            username shared: "mustFill", unique: true, size: 4..15
            email shared: "mustFill", email: true
        }

        static mapping = {
            tweets sort: 'dateCreated', order: 'desc'
        }

        static transients = {
            follows
        }

        def getFollows() {
            return users.collect { it.followed }
        }

        void addToFollows(User user) {
            UserFollower.link(user, this)
        }

        void removeFromFollows(User user) {
            UserFollower.unlink(user, this)
        }

    }

UserFollower

    class UserFollower {
        User followed
        Person follower

        static constraints = {
            followed nullable: false
            follower nullable: false
        }

        static void link(User user, Person person) {
            UserFollower f = UserFollower.findByFollowedAndFollower(user, person)
            if(!f) {
                f = new UserFollower()
                user.addToPeople(f)
                person.addToUsers(f)
                f.save()
            }
        }

        static void unlink(User user, Person person) {
            UserFollower f = UserFollower.findByFollowedAndFollower(user, person)
            if(f) {
                f = new UserFollower()
                user.removeFromPeople(f)
                person.removeFromUsers(f)
                f.delete()
            }
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM