I can not persist events with JPA

1,203 views
Skip to first unread message

Troy Hart

unread,
Jan 2, 2018, 9:14:33 PM1/2/18
to Axon Framework Users
I have been struggling all day with this issue and can't make any headway. I've tried so many different things, but I just get the following exception:

javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:282)
at com.sun.proxy.$Proxy147.persist(Unknown Source)
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at org.axonframework.eventsourcing.eventstore.jpa.JpaEventStorageEngine.appendEvents(JpaEventStorageEngine.java:360)
at org.axonframework.eventsourcing.eventstore.AbstractEventStorageEngine.appendEvents(AbstractEventStorageEngine.java:112)
at org.axonframework.eventsourcing.eventstore.AbstractEventStore.prepareCommit(AbstractEventStore.java:64)
at org.axonframework.eventhandling.AbstractEventBus.doWithEvents(AbstractEventBus.java:209)
at org.axonframework.eventhandling.AbstractEventBus.lambda$null$4(AbstractEventBus.java:144)
at org.axonframework.messaging.unitofwork.MessageProcessingContext.notifyHandlers(MessageProcessingContext.java:68)
at org.axonframework.messaging.unitofwork.DefaultUnitOfWork.notifyHandlers(DefaultUnitOfWork.java:91)
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.changePhase(AbstractUnitOfWork.java:221)
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.commitAsRoot(AbstractUnitOfWork.java:82)
at org.axonframework.messaging.unitofwork.AbstractUnitOfWork.commit(AbstractUnitOfWork.java:70)
at org.axonframework.messaging.unitofwork.DefaultUnitOfWork.executeWithResult(DefaultUnitOfWork.java:80)
at org.axonframework.commandhandling.SimpleCommandBus.handle(SimpleCommandBus.java:156)
at org.axonframework.commandhandling.AsynchronousCommandBus.lambda$handle$1(AsynchronousCommandBus.java:83)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

My (axon 3.1.1 on Spring Boot 1.5.9) application is so far very simple. It just exposes some REST endpoints for the commands and queries. When I have the following EventStorageEngine Bean definition, everything works great:

@Bean
public EventStorageEngine eventStorageEngine() {
  return new InMemoryEventStorageEngine();
}

So I can develop the the business logic, but I need to persist the events for the real system. 

So, this is what my spring boot application looks like:

package com.myco;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class FX {

  public static void main(String[] args) {
    SpringApplication.run(FX.class, args);
  }
}


I have the following application.yml configuration:


axon:
  amqp:
    exchange: FXEvents
    
spring:
  rabbitmq:
    username: test
    password: password
    host: localhost
  
# Query side Mongo Database
  data:
    mongodb:
      host: localhost
      port: 27017

# Event Store Postgres Database
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5433/fxpostgres
    username: fxpostgres
    password: supersecret
    
  jpa:
    database: POSTGRESQL
    show-sql: true
    hibernate:
      ddl-auto: create


I also have the following axon configuration class:

package com.myco;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.axonframework.commandhandling.AsynchronousCommandBus;
import org.axonframework.common.caching.Cache;
import org.axonframework.common.caching.WeakReferenceCache;
import org.axonframework.common.transaction.TransactionManager;
import org.axonframework.eventsourcing.eventstore.EventStore;
import org.axonframework.messaging.Message;
import org.axonframework.messaging.annotation.ParameterResolverFactory;
import org.axonframework.messaging.interceptors.BeanValidationInterceptor;
import org.axonframework.spring.eventsourcing.SpringAggregateSnapshotter;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
public class AxonConfig {

  public static final String EXCHANGE_NAME = "FXEvents";
  public static final String QUEUE_NAME = EXCHANGE_NAME;

  @Bean
  public BeanValidationInterceptor<Message<?>> beanValidationInteceptor() {
    return new BeanValidationInterceptor<>();
  }

  @Bean(destroyMethod = "shutdown")
  public AsynchronousCommandBus commandBus() {
    AsynchronousCommandBus cb = new AsynchronousCommandBus();
    cb.registerDispatchInterceptor(beanValidationInteceptor());
    return cb;
  }

  @Bean
  public Cache cache() {
    return new WeakReferenceCache();
  }

  @Bean
  public SpringAggregateSnapshotter snapshotter(ParameterResolverFactory parameterResolverFactory,
      EventStore eventStore, TransactionManager transactionManager) {
    Executor executor = Executors.newSingleThreadExecutor();
    return new SpringAggregateSnapshotter(eventStore, parameterResolverFactory, executor, transactionManager);
  }

  @Bean
  public Exchange exchange() {
    return ExchangeBuilder.fanoutExchange(EXCHANGE_NAME).build();
  }

  @Bean
  public Queue queue() {
    return QueueBuilder.durable(QUEUE_NAME).build();
  }

  @Bean
  public Binding binding() {
    return BindingBuilder.bind(queue()).to(exchange()).with("*").noargs();
  }

  @Autowired
  public void configure(AmqpAdmin admin) {
    admin.declareExchange(exchange());
    admin.declareQueue(queue());
    admin.declareBinding(binding());
  }
}


That's all there is to the "application" module. It has dependencies on other modules that implement the commands, queries, and some REST endpoints. The only other "configuration" to speak of exists in the externalized command modules (jar files) is the aggregate root repository configuration that looks like this:

package com.myco.pkg.command;

import org.axonframework.commandhandling.model.Repository;
import org.axonframework.common.caching.Cache;
import org.axonframework.eventsourcing.AggregateFactory;
import org.axonframework.eventsourcing.CachingEventSourcingRepository;
import org.axonframework.eventsourcing.EventCountSnapshotTriggerDefinition;
import org.axonframework.eventsourcing.Snapshotter;
import org.axonframework.eventsourcing.eventstore.EventStore;
import org.axonframework.spring.eventsourcing.SpringPrototypeAggregateFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class PkgConfig {

  @Bean
  @Scope("prototype")
  public PkgAR newPkg() {
    return new PkgAR();
  }

  @Bean
  public AggregateFactory<PkgAR> pkgAggregateFactory() {
    SpringPrototypeAggregateFactory<PkgAR> aggregateFactory = new SpringPrototypeAggregateFactory<>();
    aggregateFactory.setPrototypeBeanName("newPkg");
    return aggregateFactory;
  }

  @Bean
  public Repository<PkgAR> pkgRepository(EventStore eventStore, Cache cache, Snapshotter snapshotter) {
    CachingEventSourcingRepository<PkgAR> repository = new CachingEventSourcingRepository<>(
        pkgAggregateFactory(), eventStore, cache, new EventCountSnapshotTriggerDefinition(snapshotter, 50));
    return repository;
  }
}

Finally, there is a strange bit regarding the JPA persistence unit (persistence.xml). I initially started with the sample from the docs (https://docs.axonframework.org/part3/repositories-and-event-stores.html#jpaeventstorageengine):

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
  <persistence-unit name="eventStore" transaction-type="RESOURCE_LOCAL">
    <class>org.axonframework.eventsourcing.eventstore.jpa.DomainEventEntry</class>
    <class>org.axonframework.eventsourcing.eventstore.jpa.SnapshotEventEntry</class>
  </persistence-unit>
</persistence>

However, at my application wouldn't startup. It complained about the following unmapped entities (first one, which I fixed by adding a <class> entry to the persistence unit above, then the other): 
org.axonframework.eventhandling.saga.repository.jpa.SagaEntry
* org.axonframework.eventhandling.saga.repository.jpa.AssociationValueEntry

However, now my application doesn't care what is in that xml file or if it is even there at all. Regardless of persistence unit configuration that I provide, at startup the Hibernate DDL stuff (which I have set to automatically create the schema via: spring.jpa.hibernate.ddl-auto: create) executes to create the event store schema, and it outputs the following to the logs:

INFO LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'

Why does it say "persistence unit 'default'"? The name of the persistence unit in my application's persistence.xml is "eventStore".

Anyone know what's going on here?

Allard Buijze

unread,
Jan 3, 2018, 3:09:40 AM1/3/18
to axonfr...@googlegroups.com
Hi,

the problem is that there is no transaction running. Since you're defining a Command Bus instance explicitly, you also need to configure the transaction manager on it:

  @Bean(destroyMethod = "shutdown")
  public AsynchronousCommandBus commandBus(TransactionManager transactionManager) {
    AsynchronousCommandBus cb = new AsynchronousCommandBus();
    cb.registerDispatchInterceptor(beanValidationInteceptor());
    cb.registerHandlerInterceptor(new TransactionManagingInterceptor(transactionManager));
    return cb;
  }

Cheers,

Allard

Op wo 3 jan. 2018 om 03:14 schreef Troy Hart <troy...@gmail.com>:
--
You received this message because you are subscribed to the Google Groups "Axon Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to axonframewor...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--

Troy Hart

unread,
Jan 3, 2018, 4:24:06 PM1/3/18
to Axon Framework Users
OMG! Thank you!

I have a couple other questions too:

  1. What is the story with src/main/resources/META-INF/persistence.xml? It doesn't seem to be needed. When I don't include this file in the application, everything works (that is, now that my command bus has a registered TransactionManagingInterceptor). When I include this file, and the persistence-unit name is "eventStore" (as is documented here: https://docs.axonframework.org/part3/repositories-and-event-stores.html#jpaeventstorageengine) it doesn't matter what the contents of the file are, it simply doesn't seem to be picked up. However, when I change the name of the persistence-unit to "default", the application fails to start until I have mapped the following classes: o.a.e.e.j.DomainEventEntry, o.a.e.e.j.SnapshotEventEntry, o.a.e.s.r.j.SagaEntry and o.a.e.s.r.j.AssociationValueEntry.
  2. When I specify the the spring application property that's hibernate to just create the schema (spring.jpa.hibernate.ddl-auto: create) it both drops the table and then re-creates them. That is, it behaves as though I had specified create-drop. Is this known/expected behavior?
Cheers,

Troy

Allard Buijze

unread,
Jan 3, 2018, 4:30:51 PM1/3/18
to axonfr...@googlegroups.com
Hi Troy,

both the topics you mention are dealt with by Spring Boot. Axon doesn't do anything, other than just declare some entities and telling Spring Boot to pick them up.
I haven't written/used a persistence.xml in ages....
Regarding item 2: create is supposed to create a fresh schema, removing any data if present. Create-drop will also cause the schemas to be deleted on shutdown.

Cheers,

Allard

Op wo 3 jan. 2018 om 22:24 schreef Troy Hart <troy...@gmail.com>:
--
You received this message because you are subscribed to the Google Groups "Axon Framework Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to axonframewor...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages