JPA ManyToMany ConcurrentModificationException 问题

Gus*_*ors 6 java orm hibernate 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;
}
Run Code Online (Sandbox Code Playgroud)

没有孤儿的概念——从应用程序的角度来看,实体是“独立的”——而且大多数时候我们会有一堆 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 );
}
Run Code Online (Sandbox Code Playgroud)

如果a.getBs()这里的集合包含多个元素,则ConcurrentModificationException抛出 a。我现在一直在敲我的头一段时间,但想不出一个合理的方法来删除链接而不干预集合,这让潜在的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!无论如何都不起作用的 -loops,那就太好了,就目前而言......

Mat*_*all 5

据我所知,这个问题与 ORM 无关。不能使用foreachJava 中的语法糖构造从集合中删除元素。

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

来源

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

List<B> bs = a.getBs();
for (B b : bs)
{
    if (/* some condition */)
    {
        bs.remove(b); // throws ConcurrentModificationException
    }
}
Run Code Online (Sandbox Code Playgroud)

必须使用该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
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:我认为这会奏效;然而未经测试。如果没有,你应该停止看到ConcurrentModificationExceptions 而是(我认为)你会看到ConstraintViolationExceptions。

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
    }
}
Run Code Online (Sandbox Code Playgroud)


Gra*_*ray 2

如果此处的集合a.getBs()包含多个元素,则ConcurrentModificationException抛出a

问题是 A、B 和 C 内部的集合是神奇的 Hibernate 集合,因此当您运行以下语句时:

b.getAs().remove( a );
Run Code Online (Sandbox Code Playgroud)

这会从 b 的集合中删除 a,但也会从 a 的列表中删除 b,而 a 的列表恰好是 for 循环中正在迭代的集合。这会生成ConcurrentModificationException.

如果您确实要删除集合中的所有元素,那么马特的解决方案应该有效。如果您不这样做,那么另一种解决方法是将所有 b 复制到一个集合中,从而从进程中删除神奇的 Hibernate 集合。

// copy out of the magic hibernate collection to a local collection
List<B> copy = new ArrayList<>(a.getBs());
for (B b : copy) {
   b.getAs().remove(a) ;
   entityManager.merge(b);
}
Run Code Online (Sandbox Code Playgroud)

这应该会让你在路上走得更远。