Search code examples
javaspring-boothazelcastspring-autoconfiguration

Correct Use of Spring Boot Hazelcast Auto-Configuration with @SpringAware MapLoader


I have a project using Spring Boot 2.4.1 and Hazelcast 4.1.1. I am trying to use Spring Boot autoconfiguration to setup a distributed map with a read through using a JpaRepository to populate the map. I've added application.yaml and hazelcast.yaml and provided an implementation of com.hazelcast.map.MapLoader and com.hazelcast.map.MapLoaderLifecycleSupport annotated with @SpringAware. A hazelcast instance is started ok, but the MapLoader is never invoked. The hazelcast documentation just provides Spring XML config examples

  • Is it possible to combine Spring Boot auto config of Hazelcast with a MapLoader, or do I need to provide my own com.hazelcast.config.MapConfig and com.hazelcast.config.Config beans?
  • How do I use @SpringAware with the MapLoader?
  • What should go in the init method?
  • Do I need to register the Spring context with the Hazelcast context?

Any guidance that you can provide would be much appreciated. Below is what I've attempted so far:

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>

application.yaml:

# datasource and JPA config omitted
spring:
  hazelcast:
    config: classpath:hazelcast.yaml

hazelcast.yaml

hazelcast:
  cluster-name: hazelcast-cluster
  map:
    myResourceMap:
      map-loader:
        enabled: true
        initial-mode: EAGER
        class-name: com.dev.hz.MyResourceMaploader

MapLoader implementation:

@SpringAware
public class MyResourceMapLoader implements MapLoader<Long, MyResource>, MapLoaderLifecycleSupport {
    

    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();
    }

    @Override
    public void init(HazelcastInstance hazelcastInstance, Properties properties, String mapName) {
    }

    @Override
    public void destroy() {

    }
}   

Solution

  • One way is by having a @Component class that implements MapStoreFactory. The factory needs to implement:

    MapLoader newMapStore(String mapName, Properties properties)
    

    and can use the map name to find the relevant bean.

    Then in your @Configuration you can inject the factory, and use it to set the factory implementation on the map's map store configuration object.

    This may also be a solution, though I've not tried it.