简体   繁体   English

多对多组合的 Hibernate 复合模式

[英]Hibernate composite pattern with many-to-many composition

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.例如,我们有一个接口来实现组合,一个User类是叶节点,一个Group类构建结构。 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.但是,我不确定 Hibernate 是否支持这种设计。 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.在我看来,在加载Group Hibernate 中设置的members ,必须考虑GROUP_MEMBER表中的GROUP_MEMBER来决定要实例化哪个类。

I have considered having group containing two sets to separately fetch the groups and users, but this seems less than ideal.我曾考虑让包含两组的组分别获取组和用户,但这似乎不太理想。

May be I'm wrong, but I don't agree with having CHILD_TYPE to be part part of GROUP_MEMBER.可能是我错了,但我不同意让 CHILD_TYPE 成为 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.通过将其移动到 CHILD 表,您可以使用标准的 ManyToMany JPA 映射,这应该会使事情变得更简单。

  • If desired, CHILD_TYPE can be a discriminator inside the CHILD table.如果需要,CHILD_TYPE 可以作为 CHILD 表中的鉴别器。
  • I always recommend to have a FK.我总是建议有一个 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实现了标准的 Hibernate MTM 和继承策略
  • Common data is stored in the MEMBERS table and User specific inside USERS table (implemented using @SecondaryTable )公共数据存储在 MEMBERS 表中,用户特定于 USERS 表中(使用@SecondaryTable实现)
  • Group data is stored entirely inside MEMBERS for efficiency (eliminates JOIN), but can be extended in the same way as User组数据完全存储在 MEMBERS 内部以提高效率(消除 JOIN),但可以以与 User 相同的方式进行扩展
  • If required, an additional interface can be introduced for the isLeaf() property.如果需要,可以为isLeaf()属性引入一个额外的接口。

I think you could use a @NamedQuery looking like select g from Group g left join fetch g.members on top of your Group class and use this query with the Hibernate session.我认为您可以使用@NamedQuery看起来像select g from Group g left join fetch g.members在您的Group类之上,并在 Hibernate 会话中使用此查询。 Then you would use a query like select g from Group g left join fetch g.members where g.id = :id and get the result then.然后你会使用像select g from Group g left join fetch g.members where g.id = :id这样的查询然后得到结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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