将事件发布到Saga时没有EntityManager进行实际交易

时间:2019-03-15 15:51:24

标签: spring-boot jpa event-sourcing saga axon

我正在使用“ axon 4.0.3 + Spring Boot 2 + Spring Data(PostgreSQL)”的默认配置。

已将事件发布到EventStore并等待它被@SagaEventHandler捕获,我收到以下异常:

  

javax.persistence.TransactionRequiredException:否EntityManager具有   当前线程可用的实际事务-无法可靠地   在以下时间处理“持续”通话   org.springframework.orm.jpa.SharedEntityManagerCreator $ SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:292)   〜[spring-orm-5.1.5.RELEASE.jar:5.1.5.RELEASE]在   com.sun.proxy。$ Proxy104.persist(未知来源)〜[na:na]在   java.util.stream.ForEachOps $ ForEachOp $ OfRef.accept(ForEachOps.java:184)   〜[na:1.8.0_191]在   java.util.stream.ReferencePipeline $ 3 $ 1.accept(ReferencePipeline.java:193)   〜[na:1.8.0_191]在   java.util.ArrayList $ ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)   〜[na:1.8.0_191]在   java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)   〜[na:1.8.0_191]在   java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)   〜[na:1.8.0_191]在   java.util.stream.ForEachOps $ ForEachOp.evaluateSequential(ForEachOps.java:151)   〜[na:1.8.0_191]在   java.util.stream.ForEachOps $ ForEachOp $ OfRef.evaluateSequential(ForEachOps.java:174)   〜[na:1.8.0_191]在   java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)   〜[na:1.8.0_191]在   java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)   〜[na:1.8.0_191]在   org.axonframework.eventsourcing.eventstore.jpa.JpaEventStorageEngine.appendEvents(JpaEventStorageEngine.java:276)   〜[axon-eventsourcing-4.0.3.jar:4.0.3]在   org.axonframework.eventsourcing.eventstore.AbstractEventStorageEngine.appendEvents(AbstractEventStorageEngine.java:98)   〜[axon-eventsourcing-4.0.3.jar:4.0.3]

EventStore需要什么其他配置来处理这种情况?

PS。在该方法上添加@Transactional可以解决此问题,但是我不明白为什么这是必需的。

最小代码示例(以下端点127.0.0.1:8080/1工作,但其他127.0.0.1:8080/1未工作):

@SpringBootApplication
class TestAxonApplication

class UserId(val userId: String = IdentifierFactory.getInstance().generateIdentifier()) : Serializable

class TestCommand(@TargetAggregateIdentifier val userId: UserId)

class TestedEvent(val userId: UserId)

fun main(args: Array<String>) {
    runApplication<TestAxonApplication>(*args)
}

@RestController
@RequestMapping
class Controller(var commandGateway: CommandGateway, var eventStore: EventStore) {

    @GetMapping("/1")
    fun done(): UserId? {
        return commandGateway.sendAndWait<UserId>(TestCommand(UserId()))
    }

    @GetMapping("/2")
    fun failure() {
        eventStore.publish(
                GenericEventMessage.asEventMessage<Void>(
                        TestedEvent(UserId())
                )
        )
    }

}

@Aggregate
class User() {

    @AggregateIdentifier
    private lateinit var userId: UserId

    @CommandHandler
    constructor(cmd: TestCommand) : this() {
        AggregateLifecycle.apply(TestedEvent(cmd.userId))
    }

    @EventHandler
    fun on(event: TestedEvent) {
        this.userId = event.userId
    }

}

@Saga
@ProcessingGroup("mySaga")
class MySaga {

    @StartSaga
    @SagaEventHandler(associationProperty = "userId")
    fun start(event: TestedEvent) {
        println("DONE ${event.userId.userId}")
    }

}

2 个答案:

答案 0 :(得分:2)

调用之间的区别在于,一个调用通过命令总线,而另一个跳过它并直接发布到事件总线。默认情况下,在命令总线上配置了TransactionManager。但是,事件总线上不是这种情况。

这意味着您正在发布事件,而没有激活交易。休眠不喜欢。

解决方案是将@Transactional放在端点上,以确保在存储事件时事务处于活动状态。

答案 1 :(得分:0)

您好像忘记了设置命令总线,以便进行事务管理。像下面的示例一样添加它,它将起作用:

@Bean
public CommandBus commandBus(TransactionManager transactionManager) {
    return new SimpleCommandBus(transactionManager, NoOpMessageMonitor.INSTANCE);
}

更新

我的错,首先,我被用来与Axon 3.3一起使用(不是最新的),并且我想您正在为事件存储使用自定义配置。

Axon Spring Boot默认情况下使用内存事件存储,这就是为什么如果您不定义自定义事件,则事务将无法工作的原因。

这是您的配置中缺少的内容:

@Bean
public EventStorageEngine eventStorageEngine(EntityManagerProvider entityManagerProvider, TransactionManager transactionManager) {
    return new JpaEventStorageEngine(entityManagerProvider, transactionManager);
}

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
    return new JpaTransactionManager(emf);
}

´´´

这是我为其他提案所做的一个简单示例,并且效果很好。

记住

另一个有趣的一点是,您必须定义聚合存储库,因为Axon会尝试查找与定义的聚合匹配的存储库。

@Bean
public Repository<User> documentAggregateRepository(EventStore eventStore) {
    return new EventSourcingRepository<>(User.class, eventStore);
}

与sagas相同,您必须注册它们,否则传奇将不会触发

// remember to call the method sagaName + Configuration
// or you must set up the @Saga configurationBean name pointing this method
@Bean
public SagaConfiguration<MySaga> mySagaConfiguration() {
    return SagaConfiguration.subscribingSagaManager(MySaga.class);
}

在此示例中是事件源,但是如果您对旧版迁移很感兴趣,也许您将在迁移过程中使用GenericJpaRepository(如果您需要一个示例,请告诉我)。

HTH。