没有辅助连接的 Jpa ManyToMany 自引用 Table

Jpa ManyToMany Self Reference without auxiliar Join Table

我想告诉实体为一个字段与自身连接,它在两个方向上都不是唯一的,但看起来互联网上的所有示例都使用 Join table 或其中任何一个都很旧。

人物非规范化Table:

PersonId (Pk) | RoleId | ParentRoleId
  1               1         NULL
  2               1         NULL
  3               2          1
  4               2          1

实体(使用似乎加载空列表的映射):

@Column
private Long personId;

@Column
private Long roleId;

@Column
private Long parentRoleId;

@ManyToMany
@JoinColumn(name = "parentRoleId", referencedColumnName = "roleId", updatable = false, insertable = false)
private List<Person> personsWithParentRole;

@ManyToMany
@JoinColumn(name = "roleId", referencedColumnName = "parentRoleId", updatable = false, insertable = false)
private List<Person> personsWhoseRoleHasCurrentPersonRoleAsParent;

我想知道是否有办法映射我的案例。我知道它不是最好的拱门或最高性能的,并且可以使用不同的方法,但我只是想知道那个特定的解决方案。这是对最复杂案例的简化。

我认为在您的情况下避免加入 table 绝对不是一个好主意。您当前的解决方案是最好的。

我想你需要这样的东西:

public class Person {
    @Id
    private long id;

    @JoinTable(name = "person_links", joinColumns = {
            @JoinColumn(name = "subordinate", referencedColumnName = "id", nullable = false)}, inverseJoinColumns = {
            @JoinColumn(name = "manager", referencedColumnName = "id", nullable = false)})
    @ManyToMany
    private List<Person>subordinates;


    @ManyToMany(mappedBy = "subordinates")
    private List<Person> managers;

}

免责声明:这个答案不是确定的。为了便于阅读,我写下了答案,并且打算随着 OP 的评论进行改进。此外,代码未经过测试。


数据库设计

对于答案,我将避免 join table 并假设 table 设计如下:

  • 有两个 table:personrole 分别具有主键列 PersonIdRoleId
  • 在人 table 中,RoleIdParentRoleId 是指向相同 role.RoleId
  • 的外键
  • role table 中的其他列(例如角色之间的关系)与问题无关

JPA

实体

实体遵循 table 结构。角色实体将是一个基本实体:

@Entity
public class Role{

    // ---- JPA attributes
    @Id
    // ...
    @Column(...)
    private Long roleId;

    @OneToMany(mappedBy = "role")
    private List<Person> personsWithThisRoleAsPrimaryRole;

    @OneToMany(mappedBy = "parentRole")
    private List<Person> personsWithThisRoleAsParentRole;

    // ---- Constructor
    public Role(){
        // your initialisation

        // initialise list to avoid NullPointerException
        this.personsWithThisRoleAsPrimaryRole = new ArrayList<>();
        this.personsWithThisRoleAsParentRole = new ArrayList<>();

    }

    // getters & setters
}

绕过连接 table 的技巧是利用具有瞬态属性的 @OneToMany 关系:

@Entity
public class Person{

    // ---- JPA attributes
    @Id
    // ...
    @Column(...)
    private Long personId;

    @ManyToOne
    @JoinColumn(name = "RoleId")
    private Role role;

    @ManyToOne
    @JoinColumn(name = "ParentRoleId")
    private Role parentRole;

    // ---- Transient attributes
    @Transient
    private List<Person> personsWithParentRole;

    @Transient
    private List<Person> personsWhoseRoleHasCurrentPersonRoleAsParent;

    // ---- Constructor

    public Person(){
        // your initialisation

        // initialise list to avoid NullPointerException
        this.personsWithParentRole = new ArrayList<>();
        this.personsWhoseRoleHasCurrentPersonRoleAsParent = new ArrayList<>();
    }

    @PostLoad
    public void postLoad(){
        // during JPA initialisation, role and parentRole have been defined
        // if the value exist in the database. Consequently, we can fetch some
        // interesting info:
        if(role != null){
            personsWithParentRole.addAll(role.getPersonsWithThisRoleAsParentRole());
        }
        if(parentRole != null){
            personsWhoseRoleHasCurrentPersonRoleAsParent.addAll(parentRole.getPersonsWithThisRoleAsPrimaryRole());
        }
    }

    // getters and setters for JPA attributes

    // getters for transient attributes. It doesn't make sense to create the setters for the transient list here.
}

瞬态属性

临时属性必须小心使用,因为我遇到了很多奇特的问题。但是,它们很有用,因为您可以一次获取人员列表。如果你有类似的东西:

public List<Person> getPersonsWithParentRole{
    if(role != null){
        return role.getPersonsWithThisRoleAsParentRole();
    }
}

public List<Person> getPersonsWithParentRole{
    if(parentRole != null){
        return parentRole.getPersonsWithThisRoleAsPrimaryRole();
    }
}

它应该也能工作,但在性能方面,它可能会产生额外的无关计算。

以你为例

为了看看它是否可行,让我们做一个纸+笔的草稿:

个人table

Person | Role | ParentRoleId
------ | ---- | ------------
   1   |   1  |     null
   2   |   1  |     null
   3   |   2  |      1
   4   |   2  |      1

角色table

Role | Additional Columns
---- | ----------------
  1  |      ...
  2  |      ...

实体方面

不考虑 @PostLoad 和临时列表的个人实体:

Person | Role | ParentRoleId
------ | ---- | ------------
   1   |   1  |     null
   2   |   1  |     null
   3   |   2  |      1
   4   |   2  |      1

具有@OneToMany关系的角色实体:

Role | PersonsWithThisRoleAsPrimaryRole | PersonsWithThisRoleAsParentRole
---- | -------------------------------- | -------------------------------
  1  |           [1, 2]                 |            [3, 4]
  2  |           [3, 4]                 |            [empty]

因此,在 @postLoad 之后,您将拥有:

Person | Role | ParentRoleId | PersonsWithParentRole | PersonsWhoseRoleHasCurrentPersonRoleAsParent
------ | ---- | ------------ | --------------------- | --------------------------------------------
   1   |   1  |     null     |       [3,4]           |                 [empty]
   2   |   1  |     null     |       [3,4]           |                 [empty]
   3   |   2  |      1       |       [empty]         |                 [1, 2]
   4   |   2  |      1       |       [empty]         |                 [1, 2]

/!\ Be careful about initialisation stuff (Lazy initialisation can be tricky) /!\

希望这对您有所帮助