040. Caching - dkkahm/study-spring-jpa GitHub Wiki

Levels

  • First Level Cache
    • Persistence Context for each Transaction have first level cache
  • Second Level Cache
    • Across multiple transactions
    • Store the common information for all the users of an application

Second level cache for Service method (int -> int)

        implementation 'org.springframework.boot:spring-boot-starter-cache'
	implementation 'javax.cache:cache-api'
	implementation 'org.hibernate:hibernate-jcache'
	implementation 'org.ehcache:ehcache'
  • Service
@Service
public class WorkerService {

    private final Random random = new Random();

    @Cacheable(value = "worker1", key = "#bound")
    public int getFirstWork(int bound) {
        return random.nextInt(bound);
    }

    @Cacheable(value = "worker2", key = "#bound")
    public int getSecondWork(int bound) {
        return random.nextInt(bound);
    }
}

XML Configuration for Second level cache for Service method

  • application.properties
spring.cache.jcache.config=classpath:ehcache.xml
  • ehcache.xml
<config
        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xmlns='http://www.ehcache.org/v3'
        xsi:schemaLocation="
            http://www.ehcache.org/v3
            http://www.ehcache.org/schema/ehcache-core-3.7.xsd">
    <!-- Persistent cache directory -->
    <persistence directory="spring-boot-ehcache/cache" />
    <!-- Default cache template -->
    <cache-template name="default">
        <expiry>
            <ttl unit="seconds">30</ttl>
        </expiry>
        <listeners>
            <listener>
                <class>com.example.demo6.config.CacheLogger</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>
                <events-to-fire-on>EVICTED</events-to-fire-on>
            </listener>
        </listeners>
        <resources>
            <heap>1000</heap>
            <offheap unit="MB">10</offheap>
            <disk persistent="true" unit="MB">20</disk>
        </resources>
    </cache-template>

    <cache alias="getFirstWork" uses-template="default">
        <key-type>java.lang.Integer</key-type>
        <value-type>java.lang.Integer</value-type>
    </cache>

    <cache alias="getSecondWork" uses-template="default">
        <key-type>java.lang.Integer</key-type>
        <value-type>java.lang.Integer</value-type>
    </cache>
</config>
  • CacheLogger
public class CacheLogger implements CacheEventListener<Object, Object> {
    private final Logger LOG = LoggerFactory.getLogger(CacheLogger.class);

    @Override
    public void onEvent(CacheEvent<?, ?> cacheEvent) {
        LOG.info("Key: {} | EventType: {} | Old value: {} | New value: {}",
                cacheEvent.getKey(), cacheEvent.getType(), cacheEvent.getOldValue(),
                cacheEvent.getNewValue());
    }
}

Java Configuration for Second level cache for Service method

@Configuration
@EnableCaching
public class EhcacheConfig {
    private static final Factory<? extends CacheEntryEventFilter<Integer, Integer>> NO_FILTER = null;
    private static final boolean IS_OLD_VALUE_REQUIRED = false;
    private static final boolean IS_SYNCHRONOUS = true;

    private final javax.cache.configuration.Configuration<Integer, Integer> jcacheConfiguration =
            Eh107Configuration.fromEhcacheCacheConfiguration(
                    CacheConfigurationBuilder
                            .newCacheConfigurationBuilder(Integer.class, Integer.class, ResourcePoolsBuilder.heap(100))
                            .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(5)))
                            .build()
            );

    CacheEntryListenerConfiguration<Integer, Integer> listenerConfiguration =
            new MutableCacheEntryListenerConfiguration<>(
                    FactoryBuilder.factoryOf(CacheListener.class),
                    NO_FILTER,
                    IS_OLD_VALUE_REQUIRED,
                    IS_SYNCHRONOUS);

    @Bean
    public JCacheManagerCustomizer cacheManagerCustomizer() {
        return cm -> {
            createCache(cm, "worker1");
            createCache(cm, "worker2");
        };
    }

    private void createCache(CacheManager cm, String cacheName) {
        Cache<Integer, Integer> cache = cm.getCache(cacheName);
        if (cache == null) {
            cm.createCache(cacheName, jcacheConfiguration)
                    .registerCacheEntryListener(listenerConfiguration);
        }
    }
}
  • CacheListner
public class CacheListener implements CacheEntryCreatedListener<Integer, Integer> {
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public void onCreated(final Iterable<CacheEntryEvent<? extends Integer, ? extends Integer>> cacheEntryEvents) {
        for (CacheEntryEvent<? extends Integer, ? extends Integer> entryEvent : cacheEntryEvents) {
            log.info("Cached key: {}, with value: {}", entryEvent.getKey(), entryEvent.getValue());
        }
    }
}

Second level cache for Entity

@Entity
// @Cacheable
@org.hibernate.annotations.Cache(region = EhcacheConfig.DB_CACHE, usage = CacheConcurrencyStrategy.READ_ONLY)
public class Course {
  • EhcacheConfig
// @Configuration
// @EnableCaching
public class EhcacheConfig {
    public static final String DB_CACHE = "db_cache";
}
  • Not working for findAll()
  • Looks like working for findById()
⚠️ **GitHub.com Fallback** ⚠️