Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Framework 5 and EhCache 3.5

I tried to use EhCache 3.5 caching features in our web application based on Spring Boot 2/Spring Framework 5.

I added EHCache dependency:

    <dependency>
        <groupId>org.ehcache</groupId>
        <artifactId>ehcache</artifactId>
        <version>3.5.0</version>
    </dependency>
    <dependency>
        <groupId>javax.cache</groupId>
        <artifactId>cache-api</artifactId>
        <version>1.0.0</version>
    </dependency>

Then created ehcache.xml file in src/main/resources folder:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
    monitoring="autodetect" dynamicConfig="true">

    <cache name="orders" maxElementsInMemory="100" 
        eternal="false" overflowToDisk="false" 
        memoryStoreEvictionPolicy="LFU" copyOnRead="true"
        copyOnWrite="true" />
</ehcache>

Spring 5 reference guide does't mention EHCache usage, Spring 4 reference guide states: "Ehcache 3.x is fully JSR-107 compliant and no dedicated support is required for it."

So I created controller OrderController and REST endpoint:

@Cacheable("orders")
@GetMapping(path = "/{id}")
public Order findById(@PathVariable int id) {
    return orderRepository.findById(id);
}

Spring Boot configuration:

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

But when I call this endpoint I get an exception:

Cannot find cache named 'orders' for Builder[public org.Order org.OrderController.findById(int)] caches=[orders] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'

Then I tried to use example from Spring Framework 4:

@Bean
public CacheManager cacheManager() {
    return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}

@Bean
public EhCacheManagerFactoryBean ehCacheCacheManager() {
    EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
    cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
    cmfb.setShared(true);
    return cmfb;
}

But it doesn't compile because of exception:

The type net.sf.ehcache.CacheManager cannot be resolved. It is indirectly referenced from required .class file

Please, advise.

like image 749
Sergiy Avatar asked Dec 07 '22 15:12

Sergiy


2 Answers

There is a mix of things here. Ehcache 3, which you are using, is used with Spring through JCache.

Which is why you need to use spring.cache.jcache.config=classpath:ehcache.xml.

Then, your Ehcache configuration is indeed an Ehcache 2 configuration. So is the EhCacheCacheManager. For JCache, you should use JCacheCacheManager. But in fact, you don't even need it with an ehcache.xml.

Here are the steps to make it work

Step 1: Set the correct dependencies. Note that you don't need to specify any version as they are provided by the parent pom dependency management. javax.cache is now version 1.1.

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
</dependency>

Step 2: Add an ehcache.xml file in src/main/resources. An example below.

<?xml version="1.0" encoding="UTF-8"?>
<config
    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
    xmlns='http://www.ehcache.org/v3'
    xsi:schemaLocation="
        http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.5.xsd
        http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.5.xsd">

  <service>
    <jsr107:defaults enable-management="false" enable-statistics="true"/>
  </service>

  <cache alias="value">
    <resources>
      <heap unit="entries">2000</heap>
    </resources>
  </cache>
</config>

Step 3: An application.properties with this line is needed to find the ehcache.xml

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

Note that since JCache is found in the classpath, Spring Cache will pick it as the cache provider. So there's no need to specify spring.cache.type=jcache.

Step 4: Enable caching like you did

    @SpringBootApplication
    @EnableCaching
    public class Cache5Application {

        private int value = 0;

        public static void main(String[] args) {
            ApplicationContext context = SpringApplication.run(Cache5Application.class, args);
            Cache5Application app = context.getBean(Cache5Application.class);
            System.out.println(app.value());
            System.out.println(app.value());
        }

        @Cacheable("value")
        public int value() {
            return value++;
        }
    }
like image 68
Henri Avatar answered Dec 29 '22 15:12

Henri


You need to force it to use ehcache version 2

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.3</version>
</dependency>

To use ehcache 3:

Here is the application:

@SpringBootApplication
@EnableCaching
public class Application {

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

Here is the application.yml

spring:
  cache:
    ehcache:
      config: ehcache.xml

Here a service with a counter for testing

@Service
public class OrderService {

    public static int counter=0;

    @Cacheable("orders")
    public Order findById(Long id) {
        counter++;
        return new Order(id, "desc_" + id);
    }
}

Here is a test to prove it is using the cache:

@RunWith(SpringRunner.class)
@SpringBootTest
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

    @Test
    public void getHello() throws Exception {
        orderService.findById(1l);
        assertEquals(1, OrderService.counter);
        orderService.findById(1l);
        assertEquals(1, OrderService.counter);
        orderService.findById(2l);
        assertEquals(2, OrderService.counter);
    }
}

See here for working example.

like image 23
Essex Boy Avatar answered Dec 29 '22 15:12

Essex Boy