1. Introduction to cache

In Java, when you call a method like findUserById(ID), you should look it up from the cache before calling it. If the method is not dropped, for example, it is loaded from the database, and then added to the cache, the next call will fetch the data from the cache.

Since Spring 3.1, annotated Cache support similar to @Transactional annotated transactions has been provided, along with Cache abstraction; Benefits of using Spring Cache:

  • Provides basic Cache abstraction to facilitate switching between low-level caches.
  • Annotating caches allows caching logic to be applied transparently to business code in much the same way as transactions and requires less code to complete.
  • Automatic rollback cache when transaction rollback is provided;
  • Support more complex cache logic;

For Spring Cache abstractions, learn from the following aspects:

  • Sample CONFIGURATION based on XML and annotations
  • Example cache configuration in conjunction with Redis
  • Implement complex Cache logic

Version of the framework used in this article:

  • Spring: 4.1.0. RELEASE
  • Spring Data Redis: 1.4.0.release
  • Spring the Boot: 2.1.3. RELEASE

Two, basic configuration

Use JVM memory as a cache Provider

The Xml configuration

<! -- Object to cache -->
<bean id="bookService" class="com.ariclee.cache.xml.XmlBookService"/>

<! -- Declare cache -->
<cache:advice id="cacheAdvice" cache-manager="cacheManager" >
    <cache:caching cache="books">
        <! -- Read cache: applied to methods that read data. Methods that can be cached, such as lookup methods, first read from the cache, and then add the data to the cache if no method is called.
        <cache:cacheable method="findBook" key="#id"/>

        <! Update cache: applies to methods that write data, such as new/modified methods that call the method automatically put the corresponding data into the cache -->
        <cache:cache-put method="saveBook" key="#book.id"/>

        <! Delete a single cache: applies to methods that remove data, such as deleting a method and calling a method that removes the corresponding data from the cache -->
        <cache:cache-evict method="delete" key="id"/>

        <! Flush cache: same as above, note that all-entries default to false -->
        <cache:cache-evict method="deleteAll" all-entries="true"/>
    </cache:caching>
</cache:advice>

<! -- Cache section -->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.ariclee.cache.xml.XmlBookService.*(..) )"/>
</aop:config>

<! Register cache management (JVM cache) -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="books"/>
        </set>
    </property>
</bean>

<! Register cache management (JVM cache) -->
<bean id="cacheManager2" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager">
    <constructor-arg value="books"/>
</bean>
Copy the code

Declare the class that needs to be cached, declare the cache and configure it, and use a cache named books. Declare the method findBook that needs to be cached, and use id as the key. Register the cacheManager class and use the JVM memory as the actual cache.

Annotation configuration

Enable packet scanning using XML

<mvc:annotation-driven/>

<context:component-scan base-package="com.ariclee.cache.annotation" >
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<context:component-scan base-package="com.ariclee.cache.annotation" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

Copy the code

Using the @cacheable annotation on the findBook method, the value attribute is assigned to books, which means the name of the cache.

@Component
public class AnnotationBookService {
    private static Map<String, Book> respository = new HashMap<>();

    static {
        respository.put("1".new Book("Thinking In Java"));
        respository.put("2".new Book("EfficetiveJava"));
    }

    / / with XML
    @Cacheable(value = "books")
    public Book findBook(String id) {
        return respository.get(id);
    }
    
    / / with XML
    @CachePut(value = "books", key = "#book.id")
    public void saveBook(Book book) {
        respository.put(book.getId(), book);
    }

    / / with XML
    @CacheEvict(value = "books", key = "id", allEntries = false)
    public void delete(String id) {
        respository.remove(id);
    }

    / / with XML
    @CacheEvict(value = "books", allEntries = true)
    public void deleteAll(a) { respository.clear(); }}Copy the code

Using annotations and class configuration cache management class, ConcurrentMapCacheManager cached in memory management for spring simple class.

@EnableCaching
@Configuration
public class JvmAnnotationCacheConfigure implements CachingConfigurer {
    @Bean
    @Override
    public CacheManager cacheManager(a) {
        // Use JVM memory
       return new ConcurrentMapCacheManager("books");
    }
     / / to omit...
}
Copy the code

SptingBoot configuration

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

Use the @enablecaching annotation to EnableCaching. The yml configuration file is as follows:

spring:
  cache:
    type: simple
    cache-names: books
Copy the code

The type value of org. Springframework. Boot. Autoconfigure. Cache. CacheType class attribute.

Use Redis as the cache Provider

Need to introduce Redis client dependencies, POM:

<! -- redis client -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.5.2</version>
</dependency>
Copy the code

The Xml configuration

application.xml

<! Register cache Management (Redis)
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <property name="hostName" value="127.0.0.1"/>
    <property name="port" value="6379"/>
    <property name="password" value="2940184"/>
    <property name="timeout" value="2000"/>
</bean>

<! -- Declare RedisTemplate class -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="connectionFactory" />
</bean>

<! Declare cache management class -->
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
    <constructor-arg ref="redisTemplate" />
    <constructor-arg value="books" />
</bean>
Copy the code

Declare the Redis connection factory and the RedisTemplate. Declare the cache management class, inject redisTemplate, and set the cache name to books.

Annotation configuration

Create the Redis configuration class

@Configuration
public class RedisConfigure {
    // Declare the Redis link factory class
    @Bean
    public JedisConnectionFactory jedisConnectionFactory(a) {
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setHostName("127.0.0.1");
        factory.setPassword("123456");
        factory.setPort(6379);
        factory.setTimeout(2000);
        factory.setUsePool(false);
        factory.setPoolConfig(new JedisPoolConfig());
        factory.setDatabase(1);
        factory.setConvertPipelineAndTxResults(false);
        return factory;
    }

    // Declare the redis action template class
    @Bean
    public RedisTemplate redisTemplate(a) {
        RedisTemplate temp = new RedisTemplate();
        temp.setConnectionFactory(this.jedisConnectionFactory());
        returntemp; }}Copy the code

Create a Cache configuration class

@EnableCaching
@Configuration
public class RedisAnnotationCacheConfigure implements CachingConfigurer {
    @Autowired
    RedisTemplate redisTemplate;

    @Bean
    @Override
    public CacheManager cacheManager(a) {
        / / use Redis
        return new RedisCacheManager(redisTemplate, 
                                    Collections.singletonList("books"));
    }
    / / to omit...
}
Copy the code

SpringBoot configuration

Spring: cache: cache-names: books type: redis redis: use-key-prefix: true redis: host: 127.0.0.1 port: 6379 password: 2940184 timeout: 2000s database: 1Copy the code

Use EhCache as the cache Provider

The ehcache dependency needs to be introduced, poM:

<! -- ehcache -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.8</version>
</dependency>
Copy the code

The XML configuration

application.xml

<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="classpath:ehcache.xml"/>
</bean>

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="ehcache"/>
</bean>
Copy the code

Ehcache. XML (copy) from www.ehcache.org/ehcache.xml

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

    <defaultCache
           maxEntriesLocalHeap="0"
           eternal="false"
           timeToIdleSeconds="1200"
           timeToLiveSeconds="1200">
      <! --<terracotta/>-->
    </defaultCache>

    <cache name="books"
           maxEntriesLocalHeap="10000"
           maxEntriesLocalDisk="1000"
           eternal="false"
           diskSpoolBufferSizeMB="20"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
           memoryStoreEvictionPolicy="LFU"
           transactionalMode="off">
        <persistence strategy="localTempSwap"/>
    </cache>
</ehcache>
Copy the code

Annotation configuration

@EnableCaching
@Configuration
public class EhCacheAnnotationCacheConfigure implements CachingConfigurer {

    @Bean
    @Override
    public CacheManager cacheManager(a) {
        EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
        ClassPathResource resource = new ClassPathResource("ehcache.xml");
        factoryBean.setConfigLocation(resource);
        try {
            factoryBean.afterPropertiesSet();
        } catch (Exception e){
            e.printStackTrace();
        }

        EhCacheCacheManager manager = new EhCacheCacheManager();
        manager.setCacheManager(factoryBean.getObject());
        return manager;
    }
    / / to omit
}
Copy the code

SpringBoot configuration

Pom:

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.4</version>
</dependency>
Copy the code

Change type to ehcache in application.yml and place ehcache.xml (again) in the Resources folder:

spring:
  cache:
    cache-names: books
    type: ehcache
    ehcache:
      config: classpath:ehcache.xml
Copy the code

Three, with logical cache implementation

Conditions of the cache

XML:

<! -- Caches only the name of the parameter containing Java -->
<cache:cacheable method="findBookByName" condition="#name.contains('Java')" key="#name"/>

<! -- Do not cache the result set with default -->
<cache:cacheable method="findBookByName" unless="#result.name.contains('default')" key="#name"/>
Copy the code

Notes:

/ / with XML
@Cacheable(value = "books", condition = "#name.contains('Java')")
public Book findBookByName(String name) {}

/ / with XML
@Cacheable(value = "books", unless = "#result.name.contains('default')")
public Book findBookByName(String name) {}
Copy the code

The condition attribute, paired with the Cacheable annotation, means “if unless is true to read the cache” and applies to the input parameter, and unless means “if unless is true to not write to the cache” and applies to the return value. Pay attention to distinguish! The condition and unless attributes can also be applied to CachePut annotations:

// Write to cache when id is 10
@CachePut(value = "books", key = "#book.id", condition = "#book.id == 10")
public void saveBook(Book book) {}

// When the id is 10, the cache will not be written
@CachePut(value = "books", key = "#book.id", unless = "#book.id == 10")
public void saveBook(Book book) {}
Copy the code

Combination operation

For example, after adding a book successfully, we need to add id:book; Name: the book; Cache key value pairs of. In this case, you need to combine multiple annotation tags:

XML:

<cache:caching method="saveAndAddAllCacheMapping" cache="books">
    <cache:cache-put key="#book.id"/>
    <cache:cache-put key="#book.name"/>
</cache:caching>
Copy the code

Notes:

@Caching(
    put = {
            @CachePut(value = "books", key = "#book.id"),
            @CachePut(value = "books", key = "#book.name"})),public Book saveBook(Book book) {}
Copy the code

For example, when a book is searched by name and the first time it is placed in the cache, the cache mapping of id:book can be implemented by combining annotations like this:

@Caching(  
        cacheable = {  
                @Cacheable(value = "books", key = "#name")  
        },  
        put = {  
                @CachePut(value = "books", key = "#result.id".@CachePut(value = "books", key = "#result.name")})public Book findBookByName(String name) {}
Copy the code

SpEL

SpEL official documentation

The name location describe The sample
methodName The root object The name of the method currently being called #root.methodName
method The root object The method currently being called #root.method.name
target The root object The target object that is currently being invoked #root.target
targetClass The root object The target object class currently being invoked #root.targetClass
args The root object A list of arguments to the currently invoked method #root.args[0]
caches The root object List of caches used by the current method call (e.g. @cacheable (value={“cache1”, “cache2”})), with two caches #root.caches[0].name
argument name Execution context The arguments to the currently called method, such as findById(Long ID), can be retrieved by #id #user.id
result Execution context Return value after method invocation (only if the determination after method invocation is valid, such as’ unless ‘, beforeInvocation=false for ‘cache EVict ‘) #result

4. Expiration control

Directly through your cache provider. The cache abstraction is… well, an abstraction not a cache implementation.

Spring Cache is a feature that the abstract framework does not provide expiration control, leaving expiration implementation to the caching provider. Such as org. Springframework. Data. Redis. Cache. RedisCacheManager class provides a Map < String, Long > expires attribute can be set to specify the name of the cache expiration time.

5. Customize the cache key

Need to implement org. Springframework. Cache. The interceptor. KeyGenerator interface, autotype the generate method. Such as:

public class CustomKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        String key = this.generateKey(params);
        System.out.println("Generate cache key:" + key);
        return key;
    }

    private String generateKey(Object[] params) {
        if (params == null || params.length < 1) {
            return "";
        }
        StringJoiner stringJoiner = new StringJoiner(":");
        for (Object obj : params) {
            if (obj instanceof Book) {
                Book temp = (Book) obj;
                stringJoiner.add(temp.getId() + ":" + temp.getName());
            }
            else{ stringJoiner.add(obj.toString()); }}returnstringJoiner.toString(); }}Copy the code

Custom cache annotations

Use composite annotations

@Caching(
    put = {
            @CachePut(value = "books", key = "#book.id"),
            @CachePut(value = "books", key = "#book.name"})),public Book saveBook(Book book) {}
Copy the code

Declare a composite annotation

@Caching(
    put = {
            @CachePut(value = "books", key = "#book.id"),
            @CachePut(value = "books", key = "#book.name"})),@Target({ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Inherited  
public @interface BookCaching {  
} 
Copy the code

Use after optimization

@BookCaching
public Book saveBook(Book book) {}
Copy the code

Custom cache management class

See org. Springframework. Cache. Concurrent. ConcurrentMapCacheManager.

Eight, reference

  • www.cnblogs.com/junzi2099/p…
  • Spring Framework Reference Documentation – 4.1.0. RELEASE
  • Spring Data Redis – 1.4.0. RELEASE