簡體   English   中英

JPA ManyToMany ConcurrentModificationException問題

[英]JPA ManyToMany ConcurrentModificationException issues

我們在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都始終使用“ C”。我們已經得出結論,我們需要雙向關系,因為只要刪除實體實例,所有鏈接(聯接表中的條目)也必須刪除。 這樣完成:

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設置,我應該怎么做? (如果有的話...)

問題2:是否有一種更合理的方法來設計OR映射,以使JPA(在本例中為Hibernate提供)可以處理所有事務。 如果我們不必包括I'll be deleted now, so everybody I know, listen carefully: you don't need to know about this!那些內容,那將會是膨脹I'll be deleted now, so everybody I know, listen carefully: you don't need to know about this! -loop,無論如何都無法正常工作...

據我所知,這個問題與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 必須使用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

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

Matt是正確的,但我想我會添加一些其他信息來解決該問題。

問題在於,A,B和C內的集合是神奇的Hibernate集合,因此在運行以下語句時:

  b.getAs().remove( a );

這會從b的集合中刪除a,但也會從a的列表中刪除b,這恰好是在for循環中迭代的集合。 生成ConcurrentModificationException

如果您確實要刪除集合中的所有元素,那么Matt的解決方案應該可以工作。 但是,如果不是這樣,另一種解決方法是將所有b都復制到一個集合中,該集合將從過程中刪除神奇的Hibernate集合。

    for ( B b : new ArrayList<B>( a.getBs() )) {
       b.getAs().remove( a ) ;
       entityManager.merge( b );
    }

那應該使您走得更遠。

格雷的解決方案成功了! 對我們來說幸運的是,JPA人士似乎一直在嘗試實現Collection,因為有關List<>集合的正確使用的Sun官方文檔接近:

注意,Iterator.remove是在迭代過程中修改集合的唯一安全方法。 如果在進行迭代時以任何其他方式修改了基礎集合,則行為未指定。

我幾乎@Stateless這個異常@Stateless了,以為那意味着一個@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() )。 那是因為即使我需要從兩個集合中刪除關聯,我也只需要從一個集合中進行迭代。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM