简体   繁体   English

Jpa ManyToMany无辅助联接表的自我参考

[英]Jpa ManyToMany Self Reference without auxiliar Join Table

I'd like to tell entity to join with itself for a field that it's not unique in both directions, but it looks like all examples in internet use a Join table or either are pretty old. 我想告诉实体在两个方向上都不都是唯一的实体,但是看起来互联网上的所有示例都使用Join表,或者其中一个都已经很旧了。

Person Denormalized Table: 非正规化表:

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

Person Entity (With mappings that seem to load an empty list): 人员实体(带有似乎加载了空列表的映射):

@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;

I would like to know if there's a way to map my case. 我想知道是否有一种方法可以说明我的情况。 I know it's not the best arch or the most performant, and that one can use a different approach, but please I am just wondering about that particular solution. 我知道这不是最好的架构或性能最高的,并且可以使用其他方法,但是请问我只是想知道这种特定的解决方案。 This is a simplification from a most complex case. 这是最复杂情况的简化。

I think is definitly a bad idea to avoid a join table in your case. 我认为绝对避免在您的情况下使用联接表是一个坏主意。 Your current solution is the best. 您当前的解决方案是最好的。

I think you need something like this: 我认为您需要这样的东西:

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;

}

Disclaimer : this answer is not definitive. 免责声明 :此答案不是确定的。 I write the answer for readibility sake and it is meant to be improved along the comment with OP. 我出于可读性考虑而写答案,并且打算与OP一起进行注释。 Furthermore, code is not tested. 此外,未测试代码。


Database design 数据库设计

for the answer, I'll avoid the join table and assume that table is designed as followed: 对于答案,我将避免使用联接表,并假设该表的设计如下:

  • there are two tables: person and role with respective primary key columns PersonId and RoleId 有两个表: personrole以及各自的主键列PersonIdRoleId
  • in the person table, RoleId and ParentRoleId are foreign keys referring to the same role.RoleId 在人员表中, RoleIdParentRoleId是引用相同role.RoleId外键。
  • other columns in role table (eg relationship between roles) are irrelevant for the question role表中的其他列(例如角色之间的关系)与该问题无关

JPA JPA

Entities 实体

Entities follow table structure. 实体遵循表结构。 The Role entity would be a basic entity: 角色实体将是一个基本实体:

@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
}

The trick for bypassing the join table would be leveraging the @OneToMany relationship with a transient attribute: 绕过@OneToMany表的技巧是利用@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.
}

Transient attributes 瞬态属性

Transient attributes have to be used with care as I encountered many fancy problems. 当我遇到许多奇特的问题时,必须谨慎使用瞬态属性。 However, they are useful as you may fetch the persons list once. 但是,它们很有用,因为您可以一次获取人员列表。 If you had something like: 如果您有类似以下内容:

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

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

It should also work but performance wise, it may engender additional irrelevant calculations. 它也应该起作用,但是从性能角度来看,可能会导致其他不相关的计算。

For your example 举个例子

To see if it should work, let's do a paper+pen like draft: 要查看它是否应该工作,让我们像草稿一样做纸质笔:

Person table 人表

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

Role table 角色表

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

Entity-wise 实体明智

Person entity without considering the @PostLoad and transient lists: 人员实体,不考虑@PostLoad和瞬态列表:

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

the role entities with the @OneToMany relationships: 具有@OneToMany关系的角色实体:

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

Consequently, after @postLoad , you'll have: 因此,在@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) /!\\ /!\\注意初始化的内容(延迟初始化可能很棘手)/!\\

Hope this helps 希望这可以帮助

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

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