«ConcurrentModificationException» при обновлении моих сущностей

Nov 06 2020

Проблема

Я использую ядро ​​hibernate 5.4.11, и когда я пытаюсь обновить большое количество объектов, я сталкиваюсь с этой проблемой прямо здесь. Похоже на внутреннее исключение спящего режима.

Обновить

Я тестировал его с помощью hibernate 5.4.23, произошло то же исключение. Я также обновил свой "соединитель mysql" до последней версии "8.0.22", и он тоже не работал, то же исключение.

Описание

Вот как я обновляю свои объекты, он работает в другом потоке, что не должно быть проблемой здесь ... это в основном собственный поток для операций с базой данных, во время этой операции другие операции с базой данных не выполняются.

        // Run the database operation for updating the entities async in a new thread, return updated entities once done
        return CompletableFuture.runAsync(() -> {

            var session = database.openSession();
            session.beginTransaction();

            try {

                // Save identities first
                for (var identity : identities)
                    session.update(identity);

                // Update components, each component-type
                for(var insertedClass : insertationOrder)
                    for (var hbc : componentsPerType.get(insertedClass))
                        session.update(hbc);

                session.flush();
                session.clear();

                session.getTransaction().commit();
            } catch (Exception e){

                var messageComposer = new ExceptionMessageComposer(e);
                GameExtension.getInstance().trace("Update : "+messageComposer.toString());
                session.getTransaction().rollback();
            }

            session.close();
        }).thenApply(v -> entities);

Класс, который я пытаюсь сохранить

@Entity
@Table(name = "chunk", uniqueConstraints = {@UniqueConstraint(columnNames={"x", "y"})}, indexes = {@Index(columnList = "x,y")})
@Access(value = AccessType.FIELD)
@SelectBeforeUpdate(false)
public class Chunk extends HibernateComponent{

    public int x;
    public int y;
    public Date createdOn;

    @OneToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "chunk_identity", joinColumns = @JoinColumn(name = "identity_id"), inverseJoinColumns = @JoinColumn(name = "id"), inverseForeignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    @Fetch(FetchMode.JOIN)
    @BatchSize(size = 50)
    public Set<Identity> inChunk = new LinkedHashSet<>();

    @Transient
    public Set<ChunkLoader> loadedBy = new LinkedHashSet<>();

    public Chunk() {}
    public Chunk(int x, int y, Date createdOn) {
        this.x = x;
        this.y = y;
        this.createdOn = createdOn;
    }
}



@Entity
@Table(name = "location")
@Access(AccessType.FIELD)
public class Transform extends HibernateComponent {

    @Embedded
    public Vector2 position = new Vector2();

    @ManyToOne
    @JoinColumn(name = "chunk_id", referencedColumnName = "identity_id", updatable = false)
    @Fetch(FetchMode.JOIN)
    public Chunk chunk;

    @Transient
    public Vector2 previousPos = new Vector2();

    public Transform() {}
    public Transform(Vector2 position) { this.position = position; }
    public Transform(Vector2 position, Chunk chunk) { this.position = position; this.chunk = chunk; }
}

Исключение, которое я получаю

19:29:05,126 INFO  [ForkJoinPool.commonPool-worker-19] Extensions     - {Game}: Update : java.util.ConcurrentModificationException:
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Exception: java.util.ConcurrentModificationException
Message: *** Null ***
+--- --- ---+
Stack Trace:
+--- --- ---+
java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1493) java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1516)
org.hibernate.persister.collection.AbstractCollectionPersister.insertRows(AbstractCollectionPersister.java:1555)
org.hibernate.action.internal.CollectionUpdateAction.execute(CollectionUpdateAction.java:89)
org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348)
org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1356)
org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1343)
com.parallelorigin.persistence.database.ECSDatabase.lambda$updateEntities$16(ECSDatabase.java:242)
java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1736) java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1728)
java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Кто-нибудь знает, почему спящий режим вызывает это внутреннее исключение и что я могу сделать, чтобы решить эту проблему?

Ответы

3 ChristianBeikov Nov 07 2020 at 10:43

Вы не должны изменять список entities, identitiesили componentsкоторый вы сохраняете / обновляете, пока другой поток работает над ним.

Поток, который сохраняет / обновляет сущности, должен владеть объектами сущностей, т.е. никакой другой поток не может вмешиваться в состояние во время выполнения транзакции. Вы можете снова использовать объекты только после завершения транзакции.

Я не знаю, что делает ваше приложение, но если вам нужен доступ к объектам в основном потоке, вы можете скопировать объекты для операции очистки. Таким образом, поток, выполняющий промывку, будет правильно владеть объектами и сможет безопасно работать с ними.