4

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?

gindex
  • 285
  • 1
  • 11
  • 1
    Can you also share the Service class? Is that a concrete class or an interface? And I wonder why you don't just autowire the service bean instead of taking it as a parameter – Onur Baştürk Jul 24 '21 at 15:41
  • Service is a usual class annotated with `@Service`, see edited example. The `Srevice` **is autowired** as a parameter because of `@Bean` annotation on the `consume` method. – gindex Jul 24 '21 at 16:16
  • Check out this one please: https://www.baeldung.com/hibernate-initialize-proxy-exception – Onur Baştürk Jul 24 '21 at 16:26
  • Do you create a new Adress entity associated with the Person entity before saving the person? It would be better if we could see the sql query running in the console by changing the hibernate/jpa show sql property to true. – Onur Baştürk Jul 24 '21 at 16:29
  • I know the article. It does not provide a solution, because the session should be created automatically enabled by `@EnableTransactionManagement ` in a Spring Boot app. Moreover, as described in the question calling `service.process(person)` from a `RestController` works fine and the session is created. I think maybe declarative transaction management is not supported in functional bindings. Because the function `consume` is discovered obviously as a Bean but assigned to a corresponding binding by Spring Cloud Stream. But I'm not sure if it's true and docs do not reveal any information. – gindex Jul 24 '21 at 18:11
  • 1
    Are you using Kafka by any chance? There is some documentation on how to integrate with the normal Spring transaction support here: https://docs.spring.io/spring-kafka/reference/html/#transactions . – Bragolgirith Jul 24 '21 at 19:21
  • No, RabbitMQ. Good you are pointing this out. – gindex Jul 24 '21 at 20:03

1 Answers1

-2

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.

gindex
  • 285
  • 1
  • 11