Illegal attempt to associate a collection with two open sessions.

Hi,

i am trying to make something eventually stupid work.
It is meant to be an example for a state-stored aggregate, see https://docs.axoniq.io/reference-guide/implementing-domain-logic/command-handling/state-stored-aggregates.

The example consists of an CartAggregate marked as @Entity, backed by a @MappedSuperClass Cart, which again is linked by @OneToMany to a list of CartEntry.

I am using mysql as storage for events and aggregates.

Server startup will be fine, also first command-receival is working without issue. At this moment the list of CartEntry is empty.

But as soon as CartEntry contains at least one item i start getting:

javax.persistence.PersistenceException: org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions. Collection :
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:807)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:785)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:350)
at com.sun.proxy.$Proxy143.persist(Unknown Source)
at org.axonframework.modelling.command.GenericJpaRepository.doSaveWithLock(GenericJpaRepository.java:143)
at org.axonframework.modelling.command.GenericJpaRepository.doSaveWithLock(GenericJpaRepository.java:53)
at org.axonframework.modelling.command.LockingRepository.doSave(LockingRepository.java:149)
at org.axonframework.modelling.command.LockingRepository.doSave(LockingRepository.java:52)
at org.axonframework.modelling.command.AbstractRepository.doCommit(AbstractRepository.java:194)
at org.axonframework.modelling.command.AbstractRepository.prepareForCommit(AbstractRepository.java:180)
at org.axonframework.modelling.command.LockingRepository.prepareForCommit(LockingRepository.java:131)
at org.axonframework.modelling.command.LockingRepository.prepareForCommit(LockingRepository.java:52)
at org.axonframework.modelling.command.AbstractRepository.lambda$newInstance$0(AbstractRepository.java:86)
at org.axonframework.messaging.unitofwork.MessageProcessingContext.notifyHandlers(MessageProcessingContext.java:71)
at org.axonframework.messaging.unitofwork.DefaultUnitOfWork.notifyHandlers(DefaultUnitOfWork.java:106)
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.changePhase(AbstractUnitOfWork.java:222)
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.commitAsRoot(AbstractUnitOfWork.java:83)
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.commit(AbstractUnitOfWork.java:71)
at org.axonframework.messaging.unitofwork.DefaultUnitOfWork.executeWithResult(DefaultUnitOfWork.java:92)
at org.axonframework.commandhandling.SimpleCommandBus.handle(SimpleCommandBus.java:176)
at org.axonframework.commandhandling.SimpleCommandBus.doDispatch(SimpleCommandBus.java:141)
at org.axonframework.commandhandling.SimpleCommandBus.dispatch(SimpleCommandBus.java:110)
at org.axonframework.commandhandling.gateway.AbstractCommandGateway.send(AbstractCommandGateway.java:75)
at org.axonframework.commandhandling.gateway.DefaultCommandGateway.send(DefaultCommandGateway.java:73)
at org.axonframework.commandhandling.gateway.DefaultCommandGateway.sendAndWait(DefaultCommandGateway.java:90)
at com.lidl.mindshift.otc.commons.orchestration.CheckoutSagaTest.given_a_cart_when_cart_update_then_cart_updated(CheckoutSagaTest.java:80)

Spring Boot Config like this:

@Configuration
public class AxonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        final ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new MoneyModule());
        return objectMapper;
    }

    @Bean
    public CommandBus commandBus(TransactionManager transactionManager, AxonConfiguration axonConfiguration) {
        SimpleCommandBus commandBus = SimpleCommandBus.builder()
                .transactionManager(transactionManager)
                .messageMonitor(axonConfiguration.messageMonitor(CommandBus.class, "commandBus"))
                .build();
        commandBus.registerHandlerInterceptor(
                new CorrelationDataInterceptor<>(axonConfiguration.correlationDataProviders()));
        return commandBus;
    }

    // https://docs.axoniq.io/reference-guide/configuring-infrastructure-components/event-processing/event-bus-and-event-store
    // https://github.com/holisticon/axon-cdi/issues/13
    // https://github.com/alexmacavei/cqrs-example/blob/master/src/main/java/ro/chronos/cqrsexample/config/AxonConfiguration.java

    @PersistenceUnit
    EntityManagerFactory entityManagerFactory;

    @Bean
    public EntityManagerProvider entityManagerProvider() {
        return () -> entityManagerFactory.createEntityManager(SynchronizationType.SYNCHRONIZED);
    }

    @Bean
    public EventStore eventStore(EventStorageEngine storageEngine, AxonConfiguration configuration) {
        return EmbeddedEventStore.builder()
                .storageEngine(storageEngine)
                .messageMonitor(configuration.messageMonitor(EventStore.class, "eventStore"))
                .build();
    }

    @Bean
    public EventStorageEngine storageEngine(Serializer defaultSerializer,
                                            DataSource dataSource,
                                            @Qualifier("eventSerializer") Serializer eventSerializer,
                                            AxonConfiguration configuration,
                                            EntityManagerProvider entityManagerProvider,
                                            TransactionManager transactionManager) throws SQLException {
        return JpaEventStorageEngine.builder()
                .snapshotSerializer(defaultSerializer)
                .upcasterChain(configuration.upcasterChain())
                .dataSource(dataSource)
                .eventSerializer(eventSerializer)
                .entityManagerProvider(entityManagerProvider)
                .transactionManager(transactionManager)
                .build();
    }
}

If required i can provide some example on github, so far it might be i am overlooking something in the setup, where my persistence.xml looks like:

<persistence
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        version="2.1"
        xmlns="http://xmlns.jcp.org/xml/ns/persistence"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
        <class>org.axonframework.eventsourcing.eventstore.jpa.DomainEventEntry</class>
        <class>org.axonframework.eventsourcing.eventstore.jpa.SnapshotEventEntry</class>
        <class>org.axonframework.modelling.saga.repository.jpa.AssociationValueEntry</class>
        <class>org.axonframework.modelling.saga.repository.jpa.SagaEntry</class>

        <class>com.lidl.mindshift.otc.commons.orchestration.cart.CartAggregate</class>
        <class>com.lidl.mindshift.otc.commons.orchestration.cart.domain.CartEntry</class>

        <properties>
            <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
            <property name="javax.persistence.schema-generation.create-source" value="metadata"/>
            <property name="javax.persistence.schema-generation.drop-source" value="metadata"/>
        </properties>
    </persistence-unit>
</persistence>

Any kind of hints would be welcome.

Have you tried using the ContainerManagedEntityProvider instead? It lets Spring manage the EntityManager instances.

In fact, if you use Spring Boot, why not letting AutoConfiguration take care of wiring everything for you? At least your command bus and event store beans look pretty standard.

In that case, autowiring will also give you the correct EntityManagerProvider.

Cheers,

Allard

Thanks for the pointers:

  • ContainerManagedEntityProvider works much better.
  • Regarding the AutoConfiguration, i feel like AxonServerAutoConfiguration is quite aggressive.
    Although i removed the axon-server-connector as explicit dependency it is still somewhere from transitive dependencies.
    Overriding EventStore and CommandBus seem okaish to me.

Ill post back here, once i have the whole thing running.

Btw the persistence.xml looks really old-school, do you have such in your current best-practices or how is entity-scanning handled?

Kind regards,
Jan

Entity scanning is not part of Axon at all. That’s plain “Spring boot”. By default, it will scan for entities in packages and subpackages of your application class.

It sounds like you’re defining too many dependencies on Axon modules. You really only need one (the starter). Also, if you leave defaults as much as possible, Axon would have provided you with the correct EntityManagerProvider as well.

Consider just putting business logic classes in your application and see how the default help you. Then, only override what you really want different from the default.

Kind regards,