spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
73.63k stars 40.32k forks source link

Could not initialize Hazelcast with a dependency on a JPA repository #15359

Open JorgenRingen opened 5 years ago

JorgenRingen commented 5 years ago

Issue after upgrading from version 2.0.4 to 2.0.5+.

Startup fails with: Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cacheManager': Requested bean is currently in creation: Is there an unresolvable circular reference?

I'm instantiating a HazelcastInstance bean manually and instantiation relies upon a org.springframework.data.jpa.repository.JpaRepository bean. This causes a circular reference on the cacheManager bean during initialisation.

@Bean public FooMapStore fooMapStore(FooRepository fooRepository) {}
@Bean public Config config(FooMapStore fooMapStore) {}
@Bean public HazelcastInstance hazelcastInstance(Config config) { }

I've debugged the dependency graph and it looks like this: entityManagerFactory -> cacheManager -> JCacheCacheConfiguration -> hazelcastPropertiesCustomizer -> hazelcastInstance -> config -> fooMapStore -> fooRepository -> (inner bean)#hash -> entityManagerFactory -> cacheManager

Sample application: https://github.com/JorgenRingen/spring-boot-error-cachemanager-currently-in-creation

Any suggestions for workarounds?

snicoll commented 5 years ago

@JorgenRingen the contract with Spring Boot always has been that the cache infrastructure is available before the JPA entity manager kicks in. The main reasoning behind that is that if you don't do this and you use second level cache, Hibernate will attempt to initialize a cache manager and create a duplicate.

Several things:

I don't see how we could support both use case and having the cache infrastructure initialized by the time hibernate starts (if you're using that) makes sense to me. Can't you defer the registration of the MapStore rather than doing it from the get go?

JorgenRingen commented 5 years ago

Okey, I see we should've avoided the cycle in the first place.

Are there any preferable way of deferring the registration? As a first draft I'm doing a "lazy-lookup" from the ApplicationContext on the first call on FooRepository from FooMapStore, but it feels a bit awkward :-)

public class FooMapStore implements MapStore<Long, Foo> {

    @Autowired 
    private ApplicationContext applicationContext;
    private FooRepository fooRepository;

    @Override 
    public void store(Long key, Foo value) {
        fooRepository().findAll();
    }

    ...

    private FooRepository fooRepository() {
        if (this.fooRepository == null) {
            this.fooRepository = applicationContext.getBean(FooRepository.class);
        }
        return fooRepository;
    }
snicoll commented 5 years ago

Thanks for the feedback. That's not really what I have in mind. I was more thinking about retrieving the HazelcastInstance later and alter its configuration to register an extra store. Is that a use case that Hazelcast supports?

JorgenRingen commented 5 years ago

I thought about registering the MapStore in a BeanPostProcessor, but according to com.hazelcast.config.Config [Config instances] should not be modified after they are used to create HazelcastInstance.

The creation of the HazelcastInstance bean instantiates it (return Hazelcast.getOrCreateHazelcastInstance(config);) so a BeanPostProcessor is too late. I could also add FooRepository to FooMapStore in a BeanPostProcessor but I think that just adds another layer of confusion compared to looking it up from the ApplicationContext.

snicoll commented 5 years ago

@JorgenRingen that's not what this issue indicates. I've tried and got another Hazelcast problem that I don't really understand:

Caused by: com.hazelcast.nio.serialization.HazelcastSerializationException: There is no suitable serializer for class com.example.cachemanagercurrentlyincreationerror.FooMapStore
    at com.hazelcast.internal.serialization.impl.AbstractSerializationService.serializerFor(AbstractSerializationService.java:491) ~[hazelcast-3.9.4.jar:3.9.4]
    at com.hazelcast.internal.serialization.impl.AbstractSerializationService.writeObject(AbstractSerializationService.java:254) ~[hazelcast-3.9.4.jar:3.9.4]
    ... 44 common frames omitted

I've submitted a PR to your repro project.

JorgenRingen commented 5 years ago

Interesting. This error only occurs when the mapstore is added in the postconstruct. As far as I understand hazelcast does some kind of broadcasting of the config if the config is updated after initialisation and thats what causing the serialization issue. From stacktrace:

Caused by: com.hazelcast.nio.serialization.HazelcastSerializationException: Failed to serialize 'com.hazelcast.config.MapConfig'
    at com.hazelcast.internal.serialization.impl.SerializationUtil.handleSerializeException(SerializationUtil.java:75) ~[hazelcast-3.9.4.jar:3.9.4]
    at com.hazelcast.internal.serialization.impl.AbstractSerializationService.toBytes(AbstractSerializationService.java:161) ~[hazelcast-3.9.4.jar:3.9.4]
    at com.hazelcast.internal.serialization.impl.AbstractSerializationService.toBytes(AbstractSerializationService.java:137) ~[hazelcast-3.9.4.jar:3.9.4]
    at com.hazelcast.internal.serialization.impl.AbstractSerializationService.toData(AbstractSerializationService.java:122) ~[hazelcast-3.9.4.jar:3.9.4]
    at com.hazelcast.internal.serialization.impl.AbstractSerializationService.toData(AbstractSerializationService.java:110) ~[hazelcast-3.9.4.jar:3.9.4]
    at com.hazelcast.internal.dynamicconfig.ClusterWideConfigurationService.cloneConfig(ClusterWideConfigurationService.java:227) ~[hazelcast-3.9.4.jar:3.9.4]
    at com.hazelcast.internal.dynamicconfig.ClusterWideConfigurationService.broadcastConfigAsync(ClusterWideConfigurationService.java:219) ~[hazelcast-3.9.4.jar:3.9.4]
    at com.hazelcast.internal.dynamicconfig.ClusterWideConfigurationService.broadcastConfig(ClusterWideConfigurationService.java:199) ~[hazelcast-3.9.4.jar:3.9.4]

Notice "broadcastConfigurationAsync".

I'll try to experiment with the deferred registration in the application where the problem originally occurred a bit later. My example-app is over-simplified with regards to hazelcast in order to show the circular reference problem. Might have to do some additional configuration somewhere in order to do updates of the hazelcast-config after hazelcast initialization.

lukass77 commented 4 years ago

please re-check this issue is not a duplicate with same root cause as this one - https://github.com/spring-projects/spring-boot/issues/4960 by try to exclude - HazelcastJpaDependencyAutoConfiguration on your spring boot application

snicoll commented 4 years ago

@lukass77 the issue is open and we'd like to make sure this works without any modification on your end. So, no, it's not a duplicate.

nkavian commented 3 years ago

I have a similar issue. I want to initialize the Hazelcast Config with one setting from my database, which then I'm hit with the cyclical issue.

StefanPopa02 commented 2 months ago

Any updates on this? I'm facing a similar issue