我们在A< - >中具有三个具有双向多对多映射的实体。 B - < - > C“层次结构”是这样的(当然简化):
@Entity
Class A {
@Id int id;
@JoinTable(
name = "a_has_b",
joinColumns = {@JoinColumn(name = "a_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "b_id", referencedColumnName = "id")})
@ManyToMany
Collection<B> bs;
}
@Entity
Class B {
@Id int id;
@JoinTable(
name = "b_has_c",
joinColumns = {@JoinColumn(name = "b_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "c_id", referencedColumnName = "id")})
@ManyToMany(fetch=FetchType.EAGER,
cascade=CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
@org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
private Collection<C> cs;
@ManyToMany(mappedBy = "bs", fetch=FetchType.EAGER,
cascade={CascadeType.MERGE,CascadeType.PERSIST, CascadeType.REFRESH})
@org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
private Collection<A> as;
}
@Entity
Class C {
@Id int id;
@ManyToMany(mappedBy = "cs", fetch=FetchType.EAGER,
cascade={CascadeType.MERGE,CascadeType.PERSIST, CascadeType.REFRESH})
@org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
private Collection<B> bs;
}
没有孤儿的概念 - 从应用程序的角度来看,实体是“独立的” - 大部分时间我们都会有一把A:s,每个都有几个B:s(有些可能在A:s之间“共享”,有些1000 C:s,并非所有这些都被任何B“使用”。我们得出结论,我们需要双向关系,因为每当实体实例是删除后,所有链接(连接表中的条目)也必须删除。这是这样做的:
void removeA( A a ) {
if ( a.getBs != null ) {
for ( B b : a.getBs() ) { //<--------- ConcurrentModificationException here
b.getAs().remove( a ) ;
entityManager.merge( b );
}
}
entityManager.remove( a );
}
如果此处的集合a.getBs()
包含多个元素,则会抛出ConcurrentModificationException
。我一直在敲打我的脑袋一段时间,但想不出一个合理的方法来删除链接而不干涉集合,这使得基础Iterator
生气。
Q1:考虑到当前的ORM设置,我该怎么做呢? (如果有的话......)
Q2:有没有更合理的方法来设计OR映射,让JPA(在这种情况下由Hibernate提供)处理所有事情。如果我们不必包括那些无论如何都不起作用的I'll be deleted now, so everybody I know, listen carefully: you don't need to know about this!
- 循环,它就会膨胀......就像它一样......
答案 0 :(得分:4)
据我所知,这个问题与ORM无关。 您不能在Java中使用syntactic-sugar foreach
构造来从集合中删除元素。
请注意,
Iterator.remove
是在迭代期间修改集合的唯一安全方式;如果在迭代进行过程中以任何其他方式修改基础集合,则行为未指定。
有问题的代码的简化示例:
List<B> bs = a.getBs();
for (B b : bs)
{
if (/* some condition */)
{
bs.remove(b); // throws ConcurrentModificationException
}
}
您必须使用Iterator
版本在迭代时删除元素。 正确实施:
List<B> bs = a.getBs();
for (Iterator<B> iter = bs.iterator(); iter.hasNext();)
{
B b = iter.next();
if (/* some condition */)
{
iter.remove(); // works correctly
}
}
编辑:我认为这会奏效;但未经测试。如果没有,您应该停止查看ConcurrentModificationException
,但相反(我认为)您会看到ConstraintViolationException
s。
void removeA(A a)
{
if (a != null)
{
a.setBs(new ArrayList<B>()); // wipe out all of a's Bs
entityManager.merge(a); // synchronize the state with the database
entityManager.remove(a); // removing should now work without ConstraintViolationExceptions
}
}
答案 1 :(得分:1)
马特是正确的,但我想我会添加一些其他方法来解决这个问题。
问题是A,B和C中的集合是神奇的Hibernate集合,因此当您运行以下语句时:
b.getAs().remove( a );
这会删除b的集合,但也从列表中删除b,该列表恰好是在for循环中迭代的集合。这会生成ConcurrentModificationException
。
如果你真的删除了集合中的所有元素,那么Matt的解决方案应该可行。如果你不是,那么另一种解决方法是将所有的b复制到一个集合中,从该过程中删除神奇的Hibernate集合。
for ( B b : new ArrayList<B>( a.getBs() )) {
b.getAs().remove( a ) ;
entityManager.merge( b );
}
这应该让你在未来的路上走得更远。
答案 2 :(得分:0)
格雷的解决方案奏效了!幸运的是,对于我们来说,JPA人员似乎一直试图实施集合,因为正确的Sun文档正确使用了List<>
集合已经表明:
请注意,Iterator.remove是在迭代期间修改集合的唯一安全方法;如果在迭代进行过程中以任何其他方式修改基础集合,则行为未指定。
我只是把头发拉过这个异常,认为这意味着一个@Stateless
方法无法从它自己的类中调用另一个@Stateless
方法。我觉得很奇怪,因为我确信我在某处读到了允许嵌套的事务。因此,当我对这个异常进行搜索时,我找到了这个帖子并应用了Gray的解决方案。只有在我的情况下,我碰巧有两个必须处理的独立收藏。正如Gray指出的那样,根据Java规范从Java容器中删除成员的正确方法,您需要使用原始容器的副本进行迭代,然后在原始容器上执行remove()
很有意义。否则,原始容器的链接列表算法会混淆。
for ( Participant p2 : new ArrayList<Participant>( p1.getFollowing() )) {
p1.getFollowing().remove(p2);
getEm().merge(p1);
p2.getFollowers().remove(p1);
getEm().merge(p2);
}
注意我只复制第一个集合(p1.getFollowing()
)而不是第二个集合(p2.getFollowers()
)。这是因为我只需要从一个集合迭代,即使我需要从两个集合中删除关联。