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?
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.