Search code examples
spring-bootspring-data-jpaspring-cloud-streamspring-rabbitspring-cloud-function

No transaction is available/created if a message handler (Consumer<T>) calls a service annotated with @Transactional


The application is a Spring Boot app with Spring Data JPA and Spring Cloud Stream (RabbitMQ) defined with functional programming model.

Functional message handler calls a service:

@Configuration
class MessageHandlerConfiguration {
    @Bean
    public Consumer<Person> consume(Service service) {
        return person -> service.process(person);
    }
}

Service method persists the entity and tries to fetch lazy loaded relation:

@Service
class Service {
    //constructor injection
    private PersonRepo personRepo;
    
    @Transactional
    public void process(Person person) {
        // create personEntity
        // ...

        var personEntity = personRepo.save(personEntity);

        // throws org.hibernate.LazyInitializationException
        var addressEntity = personEntity.getAddress();
    }
}

Accessing lazy loaded entity by personEntity.getAddress() throws org.hibernate.LazyInitializationException: could not initialize proxy - no Session. So the Service is not proxied and no transaction (and session) is available in the process method. A bit of debugging has validated this assumptions.

However, if the process method is called from a rest controller, the transaction is available and the code works fine.

Moreover, the call of the process method in the message handler can be wrapped into the TransactionTemplate and this fixes issue with the missing transaction:

@Bean
public Consumer<Person> consume(Service service, TransactionTemplate transactionTemplate) {
    return person -> transactionTemplate.execute(() -> service.process(person));
}

Why is the service not proxied if called from the message handler? Does Spring Cloud Stream integrates with declarative transaction management?


Solution

  • Ok, I solved it.

    I have created a simple project to test the use case and it's working as expected. The code is available on GitHub.

    The original problem occurred in a bigger project with more complex setup and dependencies.