具有多对多组合的 Hibernate 组合模式
Hibernate composite pattern with many-to-many composition
我想创建一个树结构,其中每个节点可以有多个父节点和子节点。 (所以实际上它不是一棵树,而是一个网络)。
例如,我们有一个实现组合的接口,User
class 是叶子节点,Group
class 是构建结构。将对递归进行一些检查(将一个组添加到一个组中,该组在某处将第一个组作为父组)。
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);
}
}
我看到在数据库中实现它的最有效方法是 link table(虽然这只是一个建议,不是必需的):
TABLE GROUP_MEMBER
-------------------
PARENT_ID NUMBER
CHILD_TYPE CHAR(1)
CHILD_ID NUMBER
不过,我不确定Hibernate是否支持这种设计。在我看来,在加载 Group
中的 members
集时,Hibernate 必须考虑 GROUP_MEMBER
table 中的鉴别器来决定实例化哪个 class。
我考虑过让组包含两组来分别获取组和用户,但这似乎不太理想。
可能我错了,但我不同意 CHILD_TYPE 成为 GROUP_MEMBER 的一部分。我是 CHILD 实现细节,应该保留它。通过将其移动到 CHILD table,您可以使用标准的 ManyToMany JPA 映射,这应该会使生活更简单。
- 如果需要,CHILD_TYPE 可以作为 CHILD table.
内部的鉴别器
- 我总是建议有一个FK。错误时有发生,数据库中的孤儿总是令人头疼。
实体:
@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;
}
}
架构:
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)
);
备注:
- 实现了标准的 Hibernate MTM 和继承策略
- 公共数据存储在 MEMBERS table 中,用户特定在 USERS table 中(使用
@SecondaryTable
实现)
- 组数据完全存储在 MEMBERS 中以提高效率(消除 JOIN),但可以像 User
一样扩展
- 如果需要,可以为
isLeaf()
属性. 引入额外的接口
我认为您可以在 Group
class 之上使用看起来像 select g from Group g left join fetch g.members
的 @NamedQuery
并将此查询与 Hibernate 会话一起使用。然后你会使用像 select g from Group g left join fetch g.members where g.id = :id
这样的查询然后得到结果。
我想创建一个树结构,其中每个节点可以有多个父节点和子节点。 (所以实际上它不是一棵树,而是一个网络)。
例如,我们有一个实现组合的接口,User
class 是叶子节点,Group
class 是构建结构。将对递归进行一些检查(将一个组添加到一个组中,该组在某处将第一个组作为父组)。
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);
}
}
我看到在数据库中实现它的最有效方法是 link table(虽然这只是一个建议,不是必需的):
TABLE GROUP_MEMBER
-------------------
PARENT_ID NUMBER
CHILD_TYPE CHAR(1)
CHILD_ID NUMBER
不过,我不确定Hibernate是否支持这种设计。在我看来,在加载 Group
中的 members
集时,Hibernate 必须考虑 GROUP_MEMBER
table 中的鉴别器来决定实例化哪个 class。
我考虑过让组包含两组来分别获取组和用户,但这似乎不太理想。
可能我错了,但我不同意 CHILD_TYPE 成为 GROUP_MEMBER 的一部分。我是 CHILD 实现细节,应该保留它。通过将其移动到 CHILD table,您可以使用标准的 ManyToMany JPA 映射,这应该会使生活更简单。
- 如果需要,CHILD_TYPE 可以作为 CHILD table. 内部的鉴别器
- 我总是建议有一个FK。错误时有发生,数据库中的孤儿总是令人头疼。
实体:
@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;
}
}
架构:
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)
);
备注:
- 实现了标准的 Hibernate MTM 和继承策略
- 公共数据存储在 MEMBERS table 中,用户特定在 USERS table 中(使用
@SecondaryTable
实现) - 组数据完全存储在 MEMBERS 中以提高效率(消除 JOIN),但可以像 User 一样扩展
- 如果需要,可以为
isLeaf()
属性. 引入额外的接口
我认为您可以在 Group
class 之上使用看起来像 select g from Group g left join fetch g.members
的 @NamedQuery
并将此查询与 Hibernate 会话一起使用。然后你会使用像 select g from Group g left join fetch g.members where g.id = :id
这样的查询然后得到结果。