Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

Let’s look at an example of using Ehcache with Spring Boot. We’ll use Ehcache version 3 as this provides an implementation of a JSR-107 cache manager.

The example is a simple REST service that produces the square of a number.

2. Dependencies

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>2.1.1.RELEASE</version></dependency>
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
    <version>1.1.0</version>
</dependency>
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.6.2</version>
</dependency>     

3. Example

Let’s create a simple REST controller which calls a service to square a number and returns the result as a JSON string:

@RestController
@RequestMapping("/number", MediaType.APPLICATION_JSON_UTF8_VALUE)
public class NumberController {

    // ...

    @Autowired
    private NumberService numberService;

    @GetMapping(path = "/square/{number}")
    public String getSquare(@PathVariable Long number) {
        log.info("call numberService to square {}", number);
        return String.format("{\"square\": %s}", numberService.square(number));
    }
}

Now let’s create the service.

We annotate the method with @Cacheable so that Spring will handle the caching. As a result of this annotation, Spring will create a proxy of the NumberService to intercept calls to the square method and call Ehcache.

We need to provide the name of the cache to use and optionally the key. We can also add a condition to restrict what is cached:

@Service
public class NumberService {

    // ...
    @Cacheable(
      value = "squareCache", 
      key = "#number", 
      condition = "#number>10")
    public BigDecimal square(Long number) {
        BigDecimal square = BigDecimal.valueOf(number)
          .multiply(BigDecimal.valueOf(number));
        log.info("square of {} is {}", number, square);
        return square;
    }
}

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

@SpringBootApplication
public class Application {

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

4. Cache Configuration

We need to add Spring’s @EnableCaching annotation to a Spring bean so that Spring’s annotation-driven cache management is enabled.

Let’s create a CacheConfig class:

@Configuration
@EnableCaching
public class CacheConfig {
}

Spring’s auto-configuration finds Ehcache’s implementation of JSR-107. However, no caches are created by default.

Because neither Spring nor Ehcache looks for a default ehcache.xml file. We add the following property to tell Spring where to find it:

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

Let’s create an ehcache.xml file with a cache called squareCache:

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.ehcache.org/v3"
    xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
    xsi:schemaLocation="
            http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
            http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">

    <cache alias="squareCache">
        <key-type>java.lang.Long</key-type>
        <value-type>java.math.BigDecimal</value-type>
        <expiry>
            <ttl unit="seconds">30</ttl>
        </expiry>

        <listeners>
            <listener>
                <class>com.baeldung.cachetest.config.CacheEventLogger</class>
                <event-firing-mode>ASYNCHRONOUS</event-firing-mode>
                <event-ordering-mode>UNORDERED</event-ordering-mode>
                <events-to-fire-on>CREATED</events-to-fire-on>
                <events-to-fire-on>EXPIRED</events-to-fire-on>
            </listener>
        </listeners>

        <resources>
            <heap unit="entries">2</heap>
            <offheap unit="MB">10</offheap>
        </resources>
    </cache>

</config>

And let’s also add the cache event listener which logs both CREATED and EXPIRED cache events:

public class CacheEventLogger 
  implements CacheEventListener<Object, Object> {

    // ...

    @Override
    public void onEvent(
      CacheEvent<? extends Object, ? extends Object> cacheEvent) {
        log.info(/* message */,
          cacheEvent.getKey(), cacheEvent.getOldValue(), cacheEvent.getNewValue());
    }
}

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.

If we go to http://localhost:8080/number/square/12then we’ll get back {“square”:144}, and in the log we’ll see:

INFO [nio-8080-exec-1] c.b.cachetest.rest.NumberController : call numberService to square 12
INFO [nio-8080-exec-1] c.b.cachetest.service.NumberService : square of 12 is 144
INFO [e [_default_]-0] c.b.cachetest.config.CacheEventLogger : Cache event CREATED for item with key 12. Old value = null, New value = 144

We can see the log message from the square method of NumberService, and the CREATED event from the EventLogger. If we then refresh the browser we will only see the following added to the log:

INFO [nio-8080-exec-2] c.b.cachetest.rest.NumberController : call numberService to square 12

The log message in the square method of NumberService isn’t being invoked. This shows us that the cached value is being used.

If we wait 30 seconds for the cached item to expire and refresh the browser we’ll see an EXPIRED event, and the value added back into the cache:

INFO [nio-8080-exec-1] (...) NumberController : call numberService to square 12
INFO [e [_default_]-1] (...) CacheEventLogger : Cache event EXPIRED for item with key 12. Old value = 144,New value = null
INFO [nio-8080-exec-1] (... )NumberService : square of 12 is 144
INFO [e [_default_]-1] (...) CacheEventLogger : Cache event CREATED for item with key 12. Old value = null, New value = 144

If we enter http://localhost:8080/number/square/3 into the browser, we get the correct answer of 9, but the value isn’t cached.

This is because of the condition we used on the @Cacheable annotation to only cache values for numbers higher than 10.

6. Conclusion

In this quick tutorial, we showed how to set up Ehcache with Spring Boot.

As always, the code can be found on GitHub.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!