Hibernate composite pattern with many-to-many composition

rghome :

I want to create a tree structure where each node can have multiple parents and children. (So actually it is not really a tree but more of a network).

For example, we have an interface to implement the composition, a User class which is the leaf node and a Group class which builds the structure. There would be some check against recursion (adding a group to a group that had the first group as a parent somewhere).

interface GroupMember {
    boolean isLeaf();
}

class User implements GroupMember {
    private int id;
    private String name;
    boolean isLeaf() { return true; }
}

class Group implements GroupMember {
    private int id;
    private Set<GroupMember> members;
    boolean isLeaf() { return false; }

    public addMember(GroupMember newMember) {
        // Some check against recursion         
        members.add(newMember);
    }
}

I see the most efficient way of implementing this in the database would be to have a link table (though this is just a suggestion and not required):

TABLE GROUP_MEMBER
-------------------
PARENT_ID    NUMBER
CHILD_TYPE   CHAR(1)
CHILD_ID     NUMBER

However, I am not sure if Hibernate supports this design. It seems to me that in loading the members set in Group Hibernate would have to consider the discriminator in the GROUP_MEMBER table to decide which class to instantiate.

I have considered having group containing two sets to separately fetch the groups and users, but this seems less than ideal.

Konstantin Triger :

May be I'm wrong, but I don't agree with having CHILD_TYPE to be part part of GROUP_MEMBER. I's a CHILD implementation detail and should stay with it. By moving it to the CHILD table, you can use standard ManyToMany JPA mapping, which should make the life simpler.

  • If desired, CHILD_TYPE can be a discriminator inside the CHILD table.
  • I always recommend to have a FK. Bugs happen, and orphans in the database are always a huge headache.

Entities:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "CHILD_TYPE", length = 1)
@Table(name = "MEMBERS", schema = "mtm")
@Data //lombok
@EqualsAndHashCode(onlyExplicitlyIncluded = true) //lombok
public abstract class GroupMember {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @ManyToMany
    @JoinTable(name = "GROUP_MEMBER", schema = "mtm",
      joinColumns = @JoinColumn(name = "MEMBER_ID", referencedColumnName = "ID"),
      inverseJoinColumns = @JoinColumn(name = "PARENT_ID", referencedColumnName = "ID"))
    private Set<Group> parents = new HashSet<>();

    public abstract boolean isLeaf();
}

@Entity
@DiscriminatorValue("G")
@Data
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
class Group extends GroupMember {

    @ManyToMany(mappedBy = "parents")
    private Set<GroupMember> members = new HashSet<>();

    public boolean isLeaf() {
        return false;
    }

}

@Entity
@DiscriminatorValue("U")
@SecondaryTable(name = "USERS", schema = "mtm")
@Data
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
class User extends GroupMember {

    @EqualsAndHashCode.Include
    @Column(table = "USERS")
    private String name;

    public boolean isLeaf() {
        return true;
    }

}

Schema:

create schema if not exists MTM;

CREATE TABLE MTM.MEMBERS (
    id INT GENERATED BY DEFAULT AS IDENTITY,
    CHILD_TYPE   CHAR(1)
);

CREATE TABLE MTM.GROUP_MEMBER (
    member_id INT,
    parent_id INT
);

CREATE TABLE MTM.users (
    id INT,
    name varchar(255)
);

Notes:

  • Standard Hibernate MTM and inheritance strategies are implemented
  • Common data is stored in the MEMBERS table and User specific inside USERS table (implemented using @SecondaryTable)
  • Group data is stored entirely inside MEMBERS for efficiency (eliminates JOIN), but can be extended in the same way as User
  • If required, an additional interface can be introduced for the isLeaf() property.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=133054&siteId=1