0
votes

I'm working on a Spring Boot 2.4.1 project using spring-boot-data-jpa and hazelcast. I'm trying to set up a distributed map with a read through to a database. I've implemented com.hazelcast.map.MapLoader, but when I try to run the application it fails to start because of a circular dependency. It seems the JpaRepository needs the HazelcastInstance to be available first, but the HazelcastInstance needs the MapLoader which in turn needs the JpaRepository to be ready. At least that's what it looks like from the logs and this post.

Does anybody know how to fix this issue?

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.1</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

...

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast-all</artifactId>
    <version>4.1.1</version>
</dependency>

Config:

@Configuration
public class HZConfig {
    
    @Bean
    Config config(RVOLoader rvoLoader) {}
        Config config = new Config();
        config.getMapConfig("rvoMap").getMapStoreConfig().setImplementation(rvoLoader);
        config.getMapConfig("rvoMap").getMapStoreConfig()
            .setInitialLoadMode(MapStoreConfig.InitialLoadMode.EAGER);

        return config;
    }

    @Bean
    HazelcastInstance hazelcastInstance(Config config) {
        HazelcastInstance hz = Hazelcast.newHazelcastInstance(config);
        return hz;
    }
}

MapLoader:

@Component
public class MyResourceMapLoader implements MapLoader<Long, MyResource> {
    
    private final MyResourceRepository repo;

    public MyResourceMapLoader(MyResourceRepository repo) {
        this.repo = repo;
    }

    @Override
    public MyResource load(Long key) {
        return this.repo.findById(key).orElse(null);
    }

    @Override
    public Map<Long, MyResource> loadAll(Collection<Long> keys) {
        Map<Long, MyResource> myResourceMap = new HashMap<>();
        for (Long key : keys) {
            MyResource myResource = this.load(key);
            if (myResource != null) {
                myResourceMap.put(key, myResource);
            }
        }
        return myResourceMap;
    }

    @Override
    public Iterable<Long> loadAllKeys() {
        return this.repo.findAllIds();
    }
}   

JpaRepository:

@Repository
public interface MyResourceRepository extends JpaRepository<MyResource, Long> {

    List<MyResource> findAll();

    @Query("SELECT m.id from MyResource m")
    Iterable<Long> findAllIds();

}
1

1 Answers

1
votes

One solution,

@Bean
public Config config(YourMapStoreFactory yourMapStoreFactory) {
    ...
    config.getMapConfig("rvoMap").getMapStoreConfig().setFactoryImplementation(yourMapStoreFactory);

Make the Map Store Factory a component, not the Map Store

@Component
public class YourMapStoreFactory implements MapStoreFactory {

    @Autowired
    private MyResourceRepository repo;

    public MapLoader newMapStore(String mapName, Properties properties) {
        if (mapName.equals("rvoMap") {
            return new MyResourceMapLoader(repo);