JPA ManyToMany ConcurrentModificationException问题

时间:2011-01-07 15:37:43

标签: java hibernate orm jpa

我们在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! - 循环,它就会膨胀......就像它一样......

3 个答案:

答案 0 :(得分:4)

据我所知,这个问题与ORM无关。 您不能在Java中使用syntactic-sugar foreach构造来从集合中删除元素。

  

请注意,Iterator.remove是在迭代期间修改集合的唯一安全方式;如果在迭代进行过程中以任何其他方式修改基础集合,则行为未指定。

Source

有问题的代码的简化示例:

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())。这是因为我只需要从一个集合迭代,即使我需要从两个集合中删除关联。