Java Hibernate递归多对多关联同一个实体

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1656113/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 18:44:40  来源:igfitidea点击:

Hibernate recursive many-to-many association with the same entity

javahibernatejakarta-eeentity-relationshiphibernate-annotations

提问by Magsol

Another Hibernate question... :P

另一个休眠问题...:P

Using Hibernate's Annotations framework, I have a Userentity. Each Usercan have a collection of friends: a Collection of other Users. However, I have not been able to figure out how to create a Many-to-Many association within the Userclass consisting of a list of Users (using a user-friends intermediate table).

使用 Hibernate 的 Annotations 框架,我有一个User实体。每个人都User可以有一个朋友的集合:a Collection of other Users。但是,我一直无法弄清楚如何在UserUsers列表组成的类中创建多对多关联(使用用户朋友中间表)。

Here's the User class and its annotations:

这是 User 类及其注释:

@Entity
@Table(name="tbl_users")
public class User {

    @Id
    @GeneratedValue
    @Column(name="uid")
    private Integer uid;

    ...

    @ManyToMany(
            cascade={CascadeType.PERSIST, CascadeType.MERGE},
            targetEntity=org.beans.User.class
    )
    @JoinTable(
            name="tbl_friends",
            joinColumns=@JoinColumn(name="personId"),
            inverseJoinColumns=@JoinColumn(name="friendId")
    )
    private List<User> friends;
}

The user-friend mapping table has only two columns, both of which are foreign keys to the uidcolumn of the tbl_userstable. The two columns are personId(which should map to the current user), and friendId(which specifies the id of the current user's friend).

用户朋友的映射表只有两列,这两者都是外键到uid该列tbl_users表。两列是personId(它应该映射到当前用户)和friendId(它指定当前用户的朋友的 id)。

The problem is, the "friends" field keeps coming out null, even though I've pre-populated the friends table such that all the users in the system are friends with all the other users. I've even tried switching the relationship to @OneToMany, and it still comes out null (though the Hibernate debug output shows a SELECT * FROM tbl_friends WHERE personId = ? AND friendId = ?query, but nothing else).

问题是,“朋友”字段一直为空,即使我已经预先填充了朋友表,这样系统中的所有用户都是所有其他用户的朋友。我什至尝试将关系切换到@OneToMany,但它仍然为空(尽管 Hibernate 调试输出显示SELECT * FROM tbl_friends WHERE personId = ? AND friendId = ?查询,但没有其他内容)。

Any ideas as to how to populate this list? Thank you!

关于如何填充此列表的任何想法?谢谢!

采纳答案by ChssPly76

@ManyToMany to self is rather confusing because the way you'd normally model this differs from the "Hibernate" way. Your problem is you're missing another collection.

@ManyToMany to self 相当令人困惑,因为您通常对其进行建模的方式与“休眠”方式不同。你的问题是你错过了另一个集合。

Think of it this way - if you're mapping "author" / "book" as many-to-many, you need "authors" collection on Book and "books" collection on Author. In this case, your "User" entity represents both ends of a relationship; so you need "my friends" and "friend of" collections:

这样想 - 如果您将“作者”/“书”映射为多对多,则需要“作者”集合在书上,“书”集合在作者上。在这种情况下,您的“用户”实体代表关系的两端;所以你需要“我的朋友”和“朋友的”集合:

@ManyToMany
@JoinTable(name="tbl_friends",
 joinColumns=@JoinColumn(name="personId"),
 inverseJoinColumns=@JoinColumn(name="friendId")
)
private List<User> friends;

@ManyToMany
@JoinTable(name="tbl_friends",
 joinColumns=@JoinColumn(name="friendId"),
 inverseJoinColumns=@JoinColumn(name="personId")
)
private List<User> friendOf;

You can still use the same association table, but note that join / inverseJon columns are swapped on collections.

您仍然可以使用相同的关联表,但请注意 join / inverseJon 列在集合上交换。

The "friends" and "friendOf" collections may or may not match (depending on whether your "friendship" is always mutual) and you don't have to expose them this way in your API, of course, but that's the way to map it in Hibernate.

"friends" 和 "friendOf" 集合可能匹配也可能不匹配(取决于您的“友谊”是否总是相互的),当然,您不必在 API 中以这种方式公开它们,但这就是映射的方式它在休眠状态。

回答by rt.jar

Actually its very simple and could be achieved by following say you have following entity

实际上它非常简单,可以通过以下方式实现,例如您有以下实体

public class Human {
    int id;
    short age;
    String name;
    List<Human> relatives;



    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public short getAge() {
        return age;
    }
    public void setAge(short age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<Human> getRelatives() {
        return relatives;
    }
    public void setRelatives(List<Human> relatives) {
        this.relatives = relatives;
    }

    public void addRelative(Human relative){
        if(relatives == null)
            relatives = new ArrayList<Human>();
        relatives.add(relative);
    }




}

HBM for same:

HBM 相同:

<hibernate-mapping>
    <class name="org.know.july31.hb.Human" table="Human">
        <id name="id" type="java.lang.Integer">
            <column name="H_ID" />
            <generator class="increment" />
        </id>
        <property name="age" type="short">
            <column name="age" />
        </property>
        <property name="name" type="string">
            <column name="NAME" length="200"/>
        </property>
        <list name="relatives" table="relatives" cascade="all">
         <key column="H_ID"/>
         <index column="U_ID"/>
         <many-to-many class="org.know.july31.hb.Human" column="relation"/>
      </list>
    </class>
</hibernate-mapping>

And test case

和测试用例

import org.junit.Test;
import org.know.common.HBUtil;
import org.know.july31.hb.Human;

public class SimpleTest {

    @Test
    public void test() {
        Human h1 = new Human();
        short s = 23;
        h1.setAge(s);
        h1.setName("Ratnesh Kumar singh");
        Human h2 = new Human();
        h2.setAge(s);
        h2.setName("Praveen Kumar singh");
        h1.addRelative(h2);
        Human h3 = new Human();
        h3.setAge(s);
        h3.setName("Sumit Kumar singh");
        h2.addRelative(h3);
        Human dk = new Human();
        dk.setAge(s);
        dk.setName("D Kumar singh");
        h3.addRelative(dk);
        HBUtil.getSessionFactory().getCurrentSession().beginTransaction();
        HBUtil.getSessionFactory().getCurrentSession().save(h1);
        HBUtil.getSessionFactory().getCurrentSession().getTransaction().commit();
        HBUtil.getSessionFactory().getCurrentSession().beginTransaction();
        h1 = (Human)HBUtil.getSessionFactory().getCurrentSession().load(Human.class, 1);
        System.out.println(h1.getRelatives().get(0).getName());

        HBUtil.shutdown();
    }

}

回答by K.Nicholas

The accepted answer seems overly complicated with the @JoinTableannotations. A slightly simpler implementation needs only a mappedBy. Using mappedByindicates the owning Entity, or property, which should probably be the referencesTosince that would be considered the "friends". A ManyToManyrelationship can create a very complicated graph. Using mappedBymakes the code as so:

接受的答案似乎与@JoinTable注释过于复杂。稍微简单的实现只需要一个mappedBy. UsingmappedBy表示拥有Entity或财产,这可能应该是 ,referencesTo因为那将被视为“朋友”。一个ManyToMany关系可以创造出非常复杂的图形。使用mappedBy使代码如下:

@Entity
public class Recursion {
    @Id @GeneratedValue
    private Integer id;
    // what entities does this entity reference?
    @ManyToMany
    private Set<Recursion> referencesTo;
    // what entities is this entity referenced from?
    @ManyToMany(mappedBy="referencesTo")
    private Set<Recursion> referencesFrom;
    public Recursion init() {
        referencesTo = new HashSet<>();
        return this;
    }
    // getters, setters
}

And to use it you need to consider the owning property is the referencesTo. You only need to put relationships in that property in order for them to be referenced. When you read an Entityback, assuming you do a fetch join, JPA will create the collections for the result. When you delete an Entity, JPA will delete all the references to it.

要使用它,您需要考虑拥有的属性是referencesTo. 您只需要在该属性中放置关系即可引用它们。当您读Entity回时,假设您执行 a fetch join,JPA 将为结果创建集合。当您删除一个实体时,JPA 将删除对它的所有引用。

tx.begin();
Recursion r0 = new Recursion().init();
Recursion r1 = new Recursion().init();
Recursion r2 = new Recursion().init();
r0.getReferencesTo().add(r1);
r1.getReferencesTo().add(r2);
em.persist(r0);
em.persist(r1);
em.persist(r2);

tx.commit();
// required so that existing entities with null referencesFrom will be removed from cache.
em.clear();
for ( int i=1; i <= 3; ++i ) {
    Recursion r = em.createQuery("select distinct r from Recursion r left join fetch r.referencesTo left join fetch r.referencesFrom where id = :id", Recursion.class).setParameter("id",  i).getSingleResult();
    System.out.println(r + " To=" + Arrays.toString(r.getReferencesTo().toArray()) + " From=" + Arrays.toString(r.getReferencesFrom().toArray()) );
}
tx.begin();
em.createQuery("delete from Recursion where id = 2").executeUpdate();
tx.commit();
// required so that existing entities with referencesTo will be removed from cache.
em.clear();
Recursion r = em.createQuery("select distinct r from Recursion r left join fetch r.referencesTo left join fetch r.referencesFrom where id = :id", Recursion.class).setParameter("id",  1).getSingleResult();
System.out.println(r + " To=" + Arrays.toString(r.getReferencesTo().toArray()) + " From=" + Arrays.toString(r.getReferencesFrom().toArray()) );

Which gives the following log output (always check the generated SQL statements):

这给出了以下日志输出(始终检查生成的 SQL 语句):

Hibernate: create table Recursion (id integer not null, primary key (id))
Hibernate: create table Recursion_Recursion (referencesFrom_id integer not null, referencesTo_id integer not null, primary key (referencesFrom_id, referencesTo_id))
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: alter table Recursion_Recursion add constraint FKsi0wfuwfs0bl19jjpofw4n8pt foreign key (referencesTo_id) references Recursion
Hibernate: alter table Recursion_Recursion add constraint FKarrkuyh2v1j5qnlui2vbpl7tk foreign key (referencesFrom_id) references Recursion
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: insert into Recursion (id) values (?)
Hibernate: insert into Recursion (id) values (?)
Hibernate: insert into Recursion (id) values (?)
Hibernate: insert into Recursion_Recursion (referencesFrom_id, referencesTo_id) values (?, ?)
Hibernate: insert into Recursion_Recursion (referencesFrom_id, referencesTo_id) values (?, ?)
Hibernate: select distinct recursion0_.id as id1_2_0_, recursion2_.id as id1_2_1_, recursion4_.id as id1_2_2_, references1_.referencesFrom_id as referenc1_3_0__, references1_.referencesTo_id as referenc2_3_0__, references3_.referencesTo_id as referenc2_3_1__, references3_.referencesFrom_id as referenc1_3_1__ from Recursion recursion0_ left outer join Recursion_Recursion references1_ on recursion0_.id=references1_.referencesFrom_id left outer join Recursion recursion2_ on references1_.referencesTo_id=recursion2_.id left outer join Recursion_Recursion references3_ on recursion0_.id=references3_.referencesTo_id left outer join Recursion recursion4_ on references3_.referencesFrom_id=recursion4_.id where id=?
model.Recursion@7bdf6bb7 To=[model.Recursion@1bc53649] From=[]
Hibernate: select distinct recursion0_.id as id1_2_0_, recursion2_.id as id1_2_1_, recursion4_.id as id1_2_2_, references1_.referencesFrom_id as referenc1_3_0__, references1_.referencesTo_id as referenc2_3_0__, references3_.referencesTo_id as referenc2_3_1__, references3_.referencesFrom_id as referenc1_3_1__ from Recursion recursion0_ left outer join Recursion_Recursion references1_ on recursion0_.id=references1_.referencesFrom_id left outer join Recursion recursion2_ on references1_.referencesTo_id=recursion2_.id left outer join Recursion_Recursion references3_ on recursion0_.id=references3_.referencesTo_id left outer join Recursion recursion4_ on references3_.referencesFrom_id=recursion4_.id where id=?
model.Recursion@1bc53649 To=[model.Recursion@42deb43a] From=[model.Recursion@7bdf6bb7]
Hibernate: select distinct recursion0_.id as id1_2_0_, recursion2_.id as id1_2_1_, recursion4_.id as id1_2_2_, references1_.referencesFrom_id as referenc1_3_0__, references1_.referencesTo_id as referenc2_3_0__, references3_.referencesTo_id as referenc2_3_1__, references3_.referencesFrom_id as referenc1_3_1__ from Recursion recursion0_ left outer join Recursion_Recursion references1_ on recursion0_.id=references1_.referencesFrom_id left outer join Recursion recursion2_ on references1_.referencesTo_id=recursion2_.id left outer join Recursion_Recursion references3_ on recursion0_.id=references3_.referencesTo_id left outer join Recursion recursion4_ on references3_.referencesFrom_id=recursion4_.id where id=?
model.Recursion@42deb43a To=[] From=[model.Recursion@1bc53649]
Hibernate: delete from Recursion_Recursion where (referencesTo_id) in (select id from Recursion where id=2)
Hibernate: delete from Recursion_Recursion where (referencesFrom_id) in (select id from Recursion where id=2)
Hibernate: delete from Recursion where id=2
Hibernate: select distinct recursion0_.id as id1_2_0_, recursion2_.id as id1_2_1_, recursion4_.id as id1_2_2_, references1_.referencesFrom_id as referenc1_3_0__, references1_.referencesTo_id as referenc2_3_0__, references3_.referencesTo_id as referenc2_3_1__, references3_.referencesFrom_id as referenc1_3_1__ from Recursion recursion0_ left outer join Recursion_Recursion references1_ on recursion0_.id=references1_.referencesFrom_id left outer join Recursion recursion2_ on references1_.referencesTo_id=recursion2_.id left outer join Recursion_Recursion references3_ on recursion0_.id=references3_.referencesTo_id left outer join Recursion recursion4_ on references3_.referencesFrom_id=recursion4_.id where id=?
model.Recursion@6b739528 To=[] From=[]