简体   繁体   English

与同一实体的Hibernate递归多对多关联

[英]Hibernate recursive many-to-many association with the same entity

Another Hibernate question... :P 另一个休眠问题...:P

Using Hibernate's Annotations framework, I have a User entity. 使用Hibernate的Annotations框架,我有一个User实体。 Each User can have a collection of friends: a Collection of other User s. 每个User可以有一个朋友集合:其他User的集合。 However, I have not been able to figure out how to create a Many-to-Many association within the User class consisting of a list of User s (using a user-friends intermediate table). 但是,我还无法弄清楚如何在User类中创建一个由User列表组成的多对多关联(使用用户友好中间表)。

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 uid column of the tbl_users table. 用户-朋友映射表只有两列,这两列都是tbl_users表的uid列的外键。 The two columns are personId (which should map to the current user), and friendId (which specifies the id of the current user's friend). 两列分别是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. 问题是,即使我已经预先填充了Friends表,使得系统中的所有用户都是与所有其他用户的朋友,“ friends”字段仍然保持为空。 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 = ?查询,但SELECT * FROM tbl_friends WHERE personId = ? AND friendId = ?没有其他)。

Any ideas as to how to populate this list? 关于如何填充此列表的任何想法? Thank you! 谢谢!

@ManyToMany to self is rather confusing because the way you'd normally model this differs from the "Hibernate" way. @ManyToMany对自己来说相当令人困惑,因为您通常的建模方式不同于“休眠”方式。 Your problem is you're missing another collection. 您的问题是您缺少另一个收藏夹。

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中以这种方式公开它们,但这就是映射的方式它在休眠状态。

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();
    }

}

The accepted answer seems overly complicated with the @JoinTable annotations. @JoinTable批注似乎使所接受的答案过于复杂。 A slightly simpler implementation needs only a mappedBy . 稍微简单一些的实现只需要一个mappedBy Using mappedBy indicates the owning Entity , or property, which should probably be the referencesTo since that would be considered the "friends". 使用mappedBy表示拥有的Entity或属性,它可能应该是referencesTo因为这将被视为“朋友”。 A ManyToMany relationship can create a very complicated graph. ManyToMany关系可以创建非常复杂的图。 Using mappedBy makes the code as so: 使用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 . 要使用它,您需要考虑拥有属性是referencesTo You only need to put relationships in that property in order for them to be referenced. 您只需要在该属性中放置关系即可对其进行引用。 When you read an Entity back, assuming you do a fetch join , JPA will create the collections for the result. 当您读回Entity ,假设您执行了fetch join ,则JPA将为结果创建集合。 When you delete an Entity, JPA will delete all the references to it. 删除实体时,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=[]

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

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