Hibernate 与同一实体的递归多对多关联
Posted
技术标签:
【中文标题】Hibernate 与同一实体的递归多对多关联【英文标题】:Hibernate recursive many-to-many association with the same entity 【发布时间】:2010-12-11 23:36:24 【问题描述】:另一个休眠问题...:P
使用 Hibernate 的 Annotations 框架,我有一个 User
实体。每个User
可以有一个朋友的集合:其他User
s 的集合。但是,我无法弄清楚如何在由 User
s 列表组成的 User
类中创建多对多关联(使用用户朋友中间表)。
这是 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 = ?
查询,但没有别的)。
关于如何填充此列表的任何想法?谢谢!
【问题讨论】:
【参考方案1】:@JoinTable
注释的接受的答案似乎过于复杂。一个稍微简单的实现只需要一个mappedBy
。使用mappedBy
表示拥有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
要使用它,您需要考虑拥有属性是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 的边的唯一关系表,避免在单独的列中出现重复信息。【参考方案2】:其实它很简单,可以通过以下来实现,说你有以下实体
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();
【讨论】:
【参考方案3】:@ManyToMany to self 相当令人困惑,因为您通常对其建模的方式与“休眠”方式不同。你的问题是你错过了另一个收藏。
这样想 - 如果您将“作者”/“书”映射为多对多,则需要“书”上的“作者”集合和“作者”上的“书籍”集合。在这种情况下,您的“用户”实体代表关系的两端;所以你需要“我的朋友”和“朋友的”收藏:
@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;
您仍然可以使用相同的关联表,但请注意 join / inverseJon 列在集合上交换。
“friends”和“friendOf”集合可能匹配也可能不匹配(取决于您的“友谊”是否总是相互的),当然,您不必在 API 中以这种方式公开它们,但这就是在 Hibernate 中映射它的方法。
【讨论】:
我希望你第三次来救我 :) 它工作得很好,你的解释大大澄清了我对 E-R 的理解。非常感谢,再一次! :) 我可以合并朋友和朋友吗?如果没有,那么我在这个问题的一个实体中需要关于 jsonmanagedreference 和 jsonbackreference 的帮助:***.com/questions/24563066/… 我正在使用 Hibernate 5,一个集合就足够了 - 使用两个在 tbl_friends 中创建重复的行。 如何在 "tbl_friends" 中同时使用 "friendId" 和 "personId" 进行唯一约束? 我认为值得补充的是,如果使用同一个表,其中一个应该有一个“mappedBy”而不是@JoinTable
。 friendOf 例如:@ManyToMany(mappedBy = "friends")
。否则 hibernate 将尝试保持两者并导致java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'x-y' for key 'PRIMARY'
以上是关于Hibernate 与同一实体的递归多对多关联的主要内容,如果未能解决你的问题,请参考以下文章