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

60

又是一道Hibernate问题... :P

使用Hibernate的注释框架,我有一个User实体。每个User可以拥有一个朋友集合:其他User的集合。然而,我无法弄清如何在User类中创建一个由User列表组成的Many-to-Many关联(使用一个用户朋友中间表)。

这里是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;
}

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

问题在于,“朋友”字段始终为空,即使我预先填充了朋友表,以便系统中的所有用户都与其他用户成为朋友。我甚至尝试将关系切换到 @OneToMany,但仍然为空(尽管 Hibernate 调试输出显示了一个 SELECT * FROM tbl_friends WHERE personId = ? AND friendId = ? 查询,但没有其他内容)。

有什么想法可以填充此列表吗?谢谢!

3个回答

86

@ManyToMany自关联有些令人困惑,因为通常建模的方式与“Hibernate”方式不同。您的问题在于缺少另一个集合。

可以这样考虑 - 如果将“作者”/“书”映射为多对多,则需要在Book上拥有“authors”集合,在Author上拥有“books”集合。在这种情况下,您的“User”实体代表关系的两端; 因此,您需要 “我的朋友”和“朋友的”两个集合:

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

您仍然可以使用同一个关联表,但请注意集合中的连接/反向连接列已经交换。

"friends"和"friendOf"集合可能匹配,也可能不匹配(这取决于您的"友谊"是否始终是相互的),当然您在API中不必以此方式公开它们,但这是在Hibernate中映射它们的方式。


我希望你能第三次来拯救我 :) 它完美地运行了,你的解释显著地澄清了我的 E-R 理解。再次非常感谢你! :) - Magsol
1
我可以将friends和friendOf结合起来吗?如果不能,那么我需要在这个问题中使用jsonmanagedreference和jsonbackreference来帮助一个实体:http://stackoverflow.com/questions/24563066/hibernate-user-and-friends-jsonreference - dikkini
我正在使用Hibernate 5,一个集合就足够了 - 使用两个会在tbl_friends表中创建重复的行。 - mdziob
如何在“tbl_friends”表中使用“friendId”和“personId”两个字段实现唯一约束? - Tayfun Yaşar
2
我认为值得补充的是,如果使用同一张表,其中一个应该有“mappedBy”,而不是@JoinTable。例如_friendOf_:@ManyToMany(mappedBy = "friends")。否则,Hibernate 将尝试持久化两个对象,并导致java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'x-y' for key 'PRIMARY'异常。 - Lucas Noetzold

10
接受的答案似乎过于复杂,其中包含@JoinTable注释。稍微简单一点的实现只需要一个mappedBy即可。使用mappedBy指示拥有的实体或属性,这应该是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
}

要使用它,您需要考虑拥有属性为referencesTo。 您只需要在该属性中放置关系,以便引用它们。 当您读取一个Entity回来时,假设您执行了一个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()) );

这将产生以下日志输出(始终检查生成的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=[]

这个答案应该被接受,它提供了一个简单的1个参数解决方案。它创建了一个按照预期构建的有向图。它会创建一个唯一的关系表,该表表示从 a 到 b 的边缘,避免在不同列中重复信息。 - Simplicity's_Strength

0

实际上这很简单,只需要遵循以下步骤即可。假设你有一个实体如下:

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:

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

测试用例

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

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接