Java JPA许多ConcurrentModificationException问题
在ABC“层次结构”中,我们有三个具有双向多对多映射的实体,如下所示(当然是简化的):Java JPA许多ConcurrentModificationException问题,java,hibernate,orm,jpa,Java,Hibernate,Orm,Jpa,在ABC“层次结构”中,我们有三个具有双向多对多映射的实体,如下所示(当然是简化的): @实体 甲级{ @Id-int-Id; @可接合( name=“a_有b”, joinColumns={@JoinColumn(name=“a_id”,referencedColumnName=“id”)}, inverseJoinColumns={@JoinColumn(name=“b_id”,referencedColumnName=“id”)} @许多 收集垃圾; } @实体 B类{ @Id-int-I
@实体
甲级{
@Id-int-Id;
@可接合(
name=“a_有b”,
joinColumns={@JoinColumn(name=“a_id”,referencedColumnName=“id”)},
inverseJoinColumns={@JoinColumn(name=“b_id”,referencedColumnName=“id”)}
@许多
收集垃圾;
}
@实体
B类{
@Id-int-Id;
@可接合(
name=“b_有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)
私人收藏;
@ManyToMany(mappedBy=“bs”,fetch=FetchType.EAGER,
cascade={CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
@org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
私人收藏;
}
@实体
C类{
@Id-int-Id;
@ManyToMany(mappedBy=“cs”,fetch=FetchType.EAGER,
cascade={CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH})
@org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
私人收藏;
}
没有孤立的概念-从应用程序的角度来看,实体是“独立的”-大多数情况下,我们将有一把a:s,每个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 );
}
void removeA(A){
如果(a.getBs!=null){
就我所知,对于(B:a.getBs()){//这个问题与ORM无关。您不能使用Java中的语法sugarforeach
构造从集合中删除元素。
请注意,Iterator.remove
是在迭代过程中修改集合的唯一安全方法;如果在迭代过程中以任何其他方式修改了基础集合,则该行为未指定
问题代码的简化示例:
编辑:我认为这会起作用;但未经测试。如果没有,您应该停止看到ConcurrentModificationException
s,但(我认为)您将看到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
}
}
void removeA(A)
{
如果(a!=null)
{
a、 setBs(new ArrayList());//清除a的所有Bs
entityManager.merge(a);//将状态与数据库同步
entityManager.remove(a);//现在应该可以在没有ConstraintViolationExceptions的情况下进行删除
}
}
Matt是正确的,但我想我应该添加一些关于解决此问题的其他方法的附加信息
问题在于A、B和C中的集合是神奇的Hibernate集合,因此当您运行以下语句时:
b.getAs().remove( a );
这会从b的集合中删除a,但也会从a的列表中删除b,而a的列表恰好是在for循环中迭代的集合。这将生成ConcurrentModificationException
如果你真的要删除集合中的所有元素,Matt的解决方案应该有效。但是如果你不这样做,另一个解决办法是将所有的b复制到一个集合中,从而从过程中删除神奇的Hibernate集合
for ( B b : new ArrayList<B>( a.getBs() )) {
b.getAs().remove( a ) ;
entityManager.merge( b );
}
for(B:newarraylist(a.getBs())){
b、 删除(a);
实体管理器合并(b);
}
这应该会让你走得更远。格雷的解决方案奏效了!幸运的是,对于我们来说,JPA人员似乎一直在努力实现收藏,因为关于正确使用列表收藏的Sun官方文档表明:
请注意,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);
}
for(参与者p2:newArrayList(p1.getFollowing())){
p1.getFollowing().remove(p2);
getEm().merge(p1);
p2.getFollowers().remove(p1);
getEm().merge(p2);
}
注意,我只复制了第一个集合(p1.getFollowing()
),而不是第二个集合(p2.getFollowers()
)。这是因为我只需要从一个集合中迭代,即使我需要从两个集合中删除关联。谢谢你的回答。但我的情况真的是这样吗?我没有看到我在显式修改迭代的集合,而是从其成员字段中删除内容(aCollection
)。EntityManager.merge(tt)
是否会影响提供T
的迭代器?@Gustav:EntityManager.merge(tt)
是否会影响
b.getAs().remove( a );
for ( B b : new ArrayList<B>( a.getBs() )) {
b.getAs().remove( a ) ;
entityManager.merge( b );
}
for ( Participant p2 : new ArrayList<Participant>( p1.getFollowing() )) {
p1.getFollowing().remove(p2);
getEm().merge(p1);
p2.getFollowers().remove(p1);
getEm().merge(p2);
}