Spring Boot Ehcache Replication Example

1. Overview

Let’s look at an example of using Ehcache replication with Spring Boot having ActiveMQ as JMS support.

The example is a simple REST service that saves and query Person details.

Assumption here is the you have ActiveMQ running on localhost on default tcp port (61616).

This example is tested on Mac on ActiveMQ version 5.14.3

2. Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-jmsreplication</artifactId>
    <version>0.5</version>
</dependency>
<dependency>
    <groupId>org.apache.activemq</groupId>
    <artifactId>activemq-core</artifactId>
    <version>5.7.0</version>
</dependency>

3. Example

Let’s create a simple REST controller :

@RestController
@RequestMapping(value = "/person")
public class PersonController {

    @Autowired
    private PersonService personService;
    @RequestMapping("/queryById")
    public Person queryById(Long id) {
        Person person = personService.queryById(id);
        return person;
    }
    @RequestMapping("/savePerson")
    public Person savePerson() {
        Person person = new Person();
        person.setId(1L);
        person.setName("Naren");
        person = personService.savePerson(person);
        return person;
    }
}

Now let’s create the service.

@Service
public class PersonServiceImpl implements PersonService {
    @Autowired
    private PersonRepository personRepository;
    @Override
    @Cacheable(key = "#id", value = "person")
    public Person queryById(Long id) {
        return personRepository.findById(id).get();
    }
    @Override
    @Cacheable(key = "#person.id", value = "person")
    public Person savePerson(Person person) {
        return personRepository.save(person);
    }
}

Finally, let’s create our main Spring Boot application:

@SpringBootApplication
public class CacheApplication {
public static void main(String[] args) {
        SpringApplication.run(CacheApplication.class, args);
    }
}

4. Cache Configuration

@Configuration
@EnableCaching
public class CacheConfig {
}

entries in application.properties

spring.cache.type=ehcache
spring.cache.ehcache.config=classpath:ehcache.xml

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
  <diskStore path="java.io.tmpdir"/>
  <cacheManagerPeerProviderFactory
        class="net.sf.ehcache.distribution.jms.JMSCacheManagerPeerProviderFactory"
        properties="initialContextFactoryName=com.springboot.cache.config.TestActiveMQInitialContextFactory,
                    providerURL=tcp://PLACEHOLDER.wpsho:61616, 
                    replicationTopicConnectionFactoryBindingName=topicConnectionFactory,
                    replicationTopicBindingName=ehcache, 
                    getQueueConnectionFactoryBindingName=queueConnectionFactory,
                    getQueueBindingName=ehcacheGetQueue, 
                    topicConnectionFactoryBindingName=topicConnectionFactory,
                    topicBindingName=ehcache"
        propertySeparator="," />
  <cache name="person" maxElementsInMemory="10" eternal="false"
       timeToIdleSeconds="20" timeToLiveSeconds="20" overflowToDisk="false">
     <cacheEventListenerFactory
            class="net.sf.ehcache.distribution.jms.JMSCacheReplicatorFactory"
            properties="replicateAsynchronously=true,
                        replicatePuts=true,
                        replicateUpdates=true,
                        replicateUpdatesViaCopy=true,
                        replicateRemovals=true,
                        asynchronousReplicationIntervalMillis=1000"
            propertySeparator="," />
     <cacheLoaderFactory
            class="net.sf.ehcache.distribution.jms.JMSCacheLoaderFactory"
            properties="initialContextFactoryName=com.springboot.cache.config.TestActiveMQInitialContextFactory,
                        providerURL=tcp://PLACEHOLDER.wpsho:61616,
                        replicationTopicConnectionFactoryBindingName=topicConnectionFactory,
                        getQueueConnectionFactoryBindingName=queueConnectionFactory,
                        replicationTopicBindingName=ehcache,
                        getQueueBindingName=ehcacheGetQueue,
                        timeoutMillis=10000" />
  </cache>
</ehcache>

we need to provide implementation for TestActiveMQInitialContextFactory

public class TestActiveMQInitialContextFactory extends ActiveMQInitialContextFactory {
   public Context getInitialContext(Hashtable environment) throws NamingException {
      Map<String, Object> data = new ConcurrentHashMap<String, Object>();
      String replicationTopicConnectionFactoryBindingName = (String) environment.get(JMSUtil.TOPIC_CONNECTION_FACTORY_BINDING_NAME);
        if (replicationTopicConnectionFactoryBindingName != null) {
            try {
                data.put(replicationTopicConnectionFactoryBindingName, createConnectionFactory(environment));
            } catch (URISyntaxException e) {
                throw new NamingException("Error initialisating TopicConnectionFactory with message " + e.getMessage());
            }
        }
      String getQueueConnectionfactoryBindingName = (String) environment.get(JMSUtil.GET_QUEUE_CONNECTION_FACTORY_BINDING_NAME);
      try {
            data.put(getQueueConnectionfactoryBindingName, createConnectionFactory(environment));
        } catch (URISyntaxException e) {
            throw new NamingException("Error initialisating TopicConnectionFactory with message " + e.getMessage());
        }
      String replicationTopicBindingName = (String) environment.get(JMSUtil.REPLICATION_TOPIC_BINDING_NAME);
        String getQueueBindingName = (String) environment.get(JMSUtil.GET_QUEUE_BINDING_NAME);
        if (replicationTopicBindingName != null) {
            data.put(replicationTopicBindingName, createTopic(replicationTopicBindingName));
        }
      data.put(getQueueBindingName, createQueue(getQueueBindingName));
        return createContext(environment, data);
    }
}

5. In Action

We can use Maven to start this app by running mvn spring-boot:run.

Then open up a browser and access the REST service on port 8080.

Start with hitting http://localhost:8080/person/savePerson to populate the data in the database as well as in cache.

now hit http://localhost:8080/person/queryById?id=1 to get the data served from cache.

You can run the same app again on localhost (on different port) along with the first one to simulate the cache replication.

The full source code can be found at :

https://github.com/nmandiwal/java-spring/tree/master/spring-boot-ehcache-replication