我编写了一个自定义策略,以使用实体TrainTimetable的集合克隆实体slots

下面是该模型的摘录:

Entity('TrainTimetable') {
    list        'slots'             , ref:'Slot', composition:true
}
Entity('Slot') {
    reference   'trainTimetable'    , ref:'TrainTimetable', reverse:'TrainTimetable-slots'
}


和下面的方法:

package fr.yc.rail.backend;

import java.util.ArrayList;
import java.util.List;

import org.jspresso.framework.model.entity.IEntity;
import org.jspresso.framework.model.entity.IEntityFactory;
import org.jspresso.framework.model.entity.SmartEntityCloneFactory;

import fr.yc.rail.model.Slot;
import fr.yc.rail.model.TrainTimetable;

public class CloneTrainTimetableFactory extends SmartEntityCloneFactory {

    @Override
    public <E extends IEntity> E cloneEntity(E entityToClone,
            IEntityFactory entityFactory) {

        TrainTimetable clonedTrainTimetable = (TrainTimetable) super.cloneEntity(entityToClone, entityFactory);

        TrainTimetable trainTimetableToClone = (TrainTimetable) entityToClone;

        List<Slot> clonedSlots = new ArrayList<Slot>();

        trainTimetableToClone.getSlots().each {
            Slot clonedSlot = super.cloneEntity(it, entityFactory);
            clonedSlots.add(clonedSlot);
        }

        clonedTrainTimetable.setSlots(clonedSlots);

        return (E) clonedTrainTimetable;
    }
}


调用该方法时,下面的代码在第二次迭代时引发ConcurrentModificationException错误:

trainTimetableToClone.getSlots().each {
    Slot clonedSlot = super.cloneEntity(it, entityFactory);
    clonedSlots.add(clonedSlot);
}


在堆栈跟踪下面:

ERROR <2015-06-09 18:13:11,193> org.jspresso.framework.application.frontend.controller.AbstractFrontendController : An unexpected error occurred for user demo on session 4379dc1b.
java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at org.hibernate.collection.internal.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:774)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1378)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1372)
    at org.codehaus.groovy.runtime.dgm$149.invoke(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:45)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:108)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at fr.yc.rail.backend.CloneTrainTimetableFactory.cloneEntity(CloneTrainTimetableFactory.groovy:25)
    at org.jspresso.framework.application.backend.action.CloneComponentCollectionAction.cloneElement(CloneComponentCollectionAction.java:61)
    at org.jspresso.framework.application.backend.action.AbstractCloneCollectionAction.getAddedComponents(AbstractCloneCollectionAction.java:71)
    at org.jspresso.framework.application.backend.action.AbstractAddCollectionToMasterAction.execute(AbstractAddCollectionToMasterAction.java:78)
    at org.jspresso.framework.application.backend.AbstractBackendController.execute(AbstractBackendController.java:403)
    at org.jspresso.framework.application.frontend.controller.AbstractFrontendController.executeBackend(AbstractFrontendController.java:1536)
    at org.jspresso.framework.application.frontend.controller.AbstractFrontendController.execute(AbstractFrontendController.java:576)
    at org.jspresso.framework.application.action.AbstractAction.execute(AbstractAction.java:114)
    at org.jspresso.framework.application.frontend.controller.AbstractFrontendController.executeFrontend(AbstractFrontendController.java:1549)
    at org.jspresso.framework.application.frontend.controller.AbstractFrontendController.execute(AbstractFrontendController.java:578)
    at org.jspresso.framework.view.remote.RemoteActionFactory$ActionAdapter.actionPerformed(RemoteActionFactory.java:235)
    at org.jspresso.framework.application.frontend.controller.remote.AbstractRemoteController.handleCommand(AbstractRemoteController.java:440)
    at org.jspresso.framework.application.frontend.controller.remote.AbstractRemoteController.handleCommands(AbstractRemoteController.java:202)
    at org.jspresso.framework.application.startup.remote.RemoteStartup.handleCommands(RemoteStartup.java:88)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at net.sf.qooxdoo.rpc.RemoteCallUtils.callCompatibleMethod(RemoteCallUtils.java:469)
    at net.sf.qooxdoo.rpc.RpcServlet.handleRPC(RpcServlet.java:374)
    at net.sf.qooxdoo.rpc.RpcServlet.doPost(RpcServlet.java:481)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.jspresso.framework.util.http.HttpRequestHolder.doFilter(HttpRequestHolder.java:99)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:421)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1070)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:611)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Unknown Source)


该错误意味着我正在迭代一个大小不断变化的集合,但我不知道如何摆脱它。

最佳答案

您在TrainTimetableSlot之间具有1-N双向关联。这意味着每当您更新关联的一侧时,Jspresso都会负责更新另一侧,以保持模型的一致性,例如:


如果在插槽上设置trainTimetable属性,则该插槽将被添加到火车时间表的反向slots集合中。
如果将时隙添加到火车时间表的slots属性,则添加的时隙的trainTimetable反向属性将更新到火车时间表。


在下面的代码中:



 Slot clonedSlot = super.cloneEntity(it, entityFactory);


根据前面解释的规则,clonedSlot将添加到trainTimeTableToClone插槽中。因此,迭代的集合实际上发生了变化。

为了解决此问题,在迭代之前制作一个集合的副本就足够了,即:

trainTimetableToClone.getSlots().collect().each {

07-24 09:17