This is the second day of my participation in the August More text Challenge. For details, see:August is more challenging

I. Cache Functions of the Cache

With the accumulation of time, the number of users of the application is increasing, and the scale of data is also getting bigger and bigger. Often, database query operation will become the bottleneck of user experience. At this time, cache is often a very good way to solve this problem. Using annotations to add caching to existing Sping applications with low intrusion to improve data access performance, Springboot provides a series of automated configurations for caching, making it easy to use.

1. Main core classes

Java Caching defines five core interface, respectively is CachingProvider, CacheManager that Cache, Entry and Expiry.

CachingProvider defines how to create, configure, obtain, manage, and control multiple CacheManager managers. An application can access multiple CachingProviders at run time.

CacheManager defines the creation, configuration, acquisition, management, and control of multiple uniquely named caches that exist in the context of CacheManager. A CacheManager is owned by only one CacheProvider.

A Cache is a data structure similar to a Map that temporarily stores values indexed by keys. A Cache is owned by only one CacheManager.

An Entry is a key-value pair stored in the Cache.

Expiry Each entry stored in Cache has a defined period of validity. Once this period is exceeded, the entry is in the status of expired and once expired, the entry cannot be accessed, updated or deleted. The cache expiration date can be set by ExpiryPolicy.

2. Spring cache objects

Spring since 3.1 defines the org. Springframework. Cache. The cache and org., springframework. Cache. CacheManager unified interface to different caching technology, And support for using JCache(JSR-107) annotations to simplify our development.

The Cache interface is defined by the Cache component specification and contains a set of various operations for the Cache.

Cache interface under Spring provides various xxxCache implementation, such as RedisCache EhCacheCache, ConcurentMapCache.

Each time a method that requires caching is called, Spring checks to see if the specified target method with the specified argument has been called. If so, it retrieves the result of the method call directly from the cache. If not, it calls the method and returns the result to the user, and the next call retrieves it directly from the cache.

There are two things to keep in mind when using Spring cache abstraction:

1. Determine the methods that need to be cached and their caching strategy

2. Read the data from the previous cache from the cache

Two, several important concepts & cache annotations

Concept & Annotation role
Cache Cache interface, define cache operation, implement RedisCache, EhCacheCache, ConcurrentMapCache, etc
CacheManager Cache manager, which manages various Cache components
@Cacheable Mainly for method configuration, the results of methods can be cached based on their request parameters
@CacheEvict Clear the cache
@CachePut Ensure that the method is called and expect the result to be cached
@EnableCaching Turn on annotation-based caching
keyGenerator Key generation policy when caching data
serialize Value serialization policy when caching data

Cache SpEL available metadata

The name location describe The instance
methodName root object The name of the method currently being called #root,methodName
method root object The method currently being called root method name
target root object The target object currently being called root target
targetClass root object The target object class that is currently being called root targetClass
args root object The argument list of the currently invoked method root args[0]
caches root object The list of caches used by the current method call (such as @cacheable (value={“cache1″,”cache2”})), then there are two CachOs root caches[0]name
argument name evaluation context The name of the method parameter, either directly # parameter name, or in the form #p0 or # A0, where 0 represents the index of the parameter #a0,#P0
result evaluation context Return value after method invocation (only if the judgment after method invocation is valid, e.g. ‘unless’,’cache put’ expression ‘cache EVICT ‘expression =false) result

3. Use of Cache in Springboot, and store Cache in Redis

First, the project directory:

Add dependencies first

<dependency>            
<groupId>org.springframework.boot</groupId>            
<artifactId>spring-boot-starter-cache</artifactId>       
</dependency>
Copy the code

Add the configuration to the main startup class to enable caching

package com.sense; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @MapperScan(basePackages = "com.sense.dao") @EnableCaching public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}Copy the code

Creating an entity class

package com.sense.entity;
​
import lombok.Data;
​
/**
 @desc ...
 @date 2021-07-20 14:37:35
 @author 
 */
@Data
public class User {
   private int id;
   private String username;
   private int password;
}
Copy the code

Create dao


package com.sense.dao;
​
import com.sense.entity.User;
import org.apache.ibatis.annotations.Mapper;
​
import java.util.List;
​
/**
 @desc ...
 @date 2021-08-02 10:42:33
 @author 
 */
@Mapper
public interface UserDao {
    List<User> getUser();
​
    User getUserById(Integer id);
​
    void update(User user);
​
    void deleteUser(Integer id);
​
    User queryByName(String name);
}
Copy the code

Create a service


package com.sense.service;
​
import com.sense.entity.User;
​
import java.util.List;
​
/**
 @desc ...
 @date 2021-08-02 10:37:24
 @author 
 */
public interface UserService {
    List<User> getUser();
​
    User getUserById(Integer id);
​
    void update(User user);
​
    void deleteUser(Integer id);
​
    User queryByName(String name);
}
Copy the code

Create impl

package com.sense.service.impl; import com.sense.dao.UserDao; import com.sense.entity.User; import com.sense.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.*; import org.springframework.stereotype.Service; import java.util.List; /** @desc ... @date 2021-08-02 10:40:48@author */ @service @slf44j @cacheconfig (cacheNames = "user" UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public List<User> getUser() { return userDao.getUser(); } /** * @desc@cacheable: cacheNames/value: specifies the name of the component concerned * Key: specifies the key used to cache data. * cacheManager and cacheResolver function the same way. Specify the cacheManager. Either of the following options is available: * condition: If (condition="#id>3"); if (condition="#id>3"); if (condition="#id>3"); Whether to use asynchronous mode * @ param * @ return Java. Util. List < com. Sense. Dingtalkdemo. Entity. The User > * * @ date 2021/7/30 then * / @ Override @cacheable (cacheNames = "user",key = "#id") public user getUserById(Integer ID) {log.info(" get "+id+" info "); return userDao.getUserById(id); } /** * @desc @cacheput must be used in conjunction with @cacheable, otherwise it makes no sense * @cacheput: Call method, update cache data * modify database data, and update cache * run time: * 1, call the target method, * 2, cache the result of the target method return 1 * test step: * 1. Query # 1's personal information * 2, query # 1's personal information * 3, update # 1's personal information * 4, query # 1's personal information * 4, return the result * should be updated employee * but only update the database, but not update the cache why? * 5, * @cachePUT (cacheNames="user",key="#user.id") * @cachePUT (cacheNames="user",key="#result.id") * @param user 1 * @return com.sense.dingtalkdemo.entity.User * * @date 2021/7/30 13:47 */ @Override @CachePut(cacheNames = "User ",key = "#result.id") public void update(user user) {system.out.println (" modify "+ user.getid ()+" information "); userDao.update(user); } /** * @descac@cacheevict: Clear the cache * 1.key: Specify the data to be cleared * 2.allEntries=true: Delete all data in the cache ** ** @descac@cacheevict: Clear the cache * 1.key: Specify the data to be cleared * 2.allEntries=true * beforeInvocation=true; Clearing the cache before the method is executed Only clears the cache, * @param ID 1 * @return void * * @date 2021/7/30 14:10 */ @override // @cacheevict (cacheNames = "user",key = "#id") @CacheEvict(cacheNames = "user",allEntries = true) public void deleteUser(Integer id){ System.out.println(" delete "+id+" info "); Userdao.deleteuser (id); userdao.deleteUser (id); /** * @desc beforeInvocation=true */ / Int a=1/0; /** * @desc beforeInvocation= True */ } /** * @desc;} /** * @desc;} Now, if I use id,age is going to query the cache directly, Instead of querying the database * @ param name 1 * @ return com. Sense. Dingtalkdemo. Entity. The User * * @ date 2021/7/30 * / @ Override to them @Caching(cacheable = {@Cacheable(value = "user",key = "#name")}, put = { @CachePut(value = "user",key = "#result.id"), @CachePut(value = "user",key = "#result.password")}) public User queryByName(String name) { System.out.println(" query name "+name); return userDao.queryByName(name); }}Copy the code

Creating the config

package com.sense.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** @desc ... @date 2021-08-02 09:39:44 @author */ @Configuration public class RedisConfig extends CachingConfigurerSupport { /** * @desc Configure a custom redisTemplate * @param connectionFactory 1 * @return org.springframework.data.redis.core.RedisTemplate<java.lang.String, java.lang.Object> */ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); template.setValueSerializer(jackson2JsonRedisSerializer()); / / use StringRedisSerializer to serialization and deserialization redis key values in the template. SetKeySerializer (new StringRedisSerializer ()); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(jackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; @ desc} / * * * * @ param * @ return json serialization org. Springframework. Data. Redis. Serializer. RedisSerializer <? > * * @date 2021/8/2 9:51 */ @Bean public RedisSerializer<? > jackson2JsonRedisSerializer () {/ / use jackson2JsonRedisSerializer to serialization and deserialization value of Redis values Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); return serializer; } / * * * * @ @ desc configuration cache manager param redisConnectionFactory 1 * @ return org. Springframework. Cache. CacheManager * / @ Bean public CacheManager CacheManager (RedisConnectionFactory RedisConnectionFactory) {// generate a default configuration, Through the config object can cache for custom configuration RedisCacheConfiguration config. = RedisCacheConfiguration defaultCacheConfig (); // Set the default expiration time of the cache, Config = config.entryTTL (duration.ofminutes (1)) // Set key to string serialization .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // Set value to json serialization SerializeValuesWith (RedisSerializationContext. SerializationPair. FromSerializer (jackson2JsonRedisSerializer ())) / / no cache a null value .disableCachingNullValues(); Set<String> cacheNames = new HashSet<>(); cacheNames.add("timeGroup"); cacheNames.add("user"); // Apply a different configuration for each cache space Map<String, RedisCacheConfiguration> configMap = new HashMap<>(); configMap.put("timeGroup", config); configMap.put("user", config.entryTtl(Duration.ofSeconds(120))); / / the use of a custom cache configuration initialization a cacheManager RedisCacheManager cacheManager = RedisCacheManager. Builder (redisConnectionFactory) / / must call this method first sets the initialization of the cache, to initialize the relevant configuration. InitialCacheNames (cacheNames). WithInitialCacheConfigurations (configMap). The build (); return cacheManager; } / * * * @ desc cache key is a package name + method name and argument list * @ return org. Springframework. Cache. The interceptor. @ date 2021/8/2 KeyGenerator * and * / @Override @Bean public KeyGenerator keyGenerator() { return (target, method, objects) -> { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append("::" + method.getName() + ":"); for (Object obj : objects) { sb.append(obj.toString()); } return sb.toString(); }; }}Copy the code

Creating an XML file

<? The XML version = "1.0" encoding = "utf-8"? > <! DOCTYPE mapper PUBLIC "- / / mybatis.org//DTD mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > < mapper namespace="com.sense.dao.UserDao"> <select id="getUser" resultType="com.sense.entity.User"> select * from user </select>  <select id="getUserById" resultType="com.sense.entity.User"> select * from user where id = #{id} </select> <update id="update" parameterType="com.sense.entity.User"> update user set username=#{username}, password=#{password} where id = #{id} </update> <delete id="deleteUser" parameterType="java.lang.Integer"> delete from user where id=#{id} </delete> <select id="queryByName" resultType="com.sense.entity.User"> select * from user where username = #{username} </select> </mapper>Copy the code

The introduction of Ehcache

Some notes about Ehcahe:

  • Name: Indicates the cache name.
  • MaxElementsInMemory: Indicates the maximum number of cache
  • MaxElementsOnDisk: indicates the maximum number of cached disks.
  • Eternal: whether the object is permanent, once set, timeout will have no effect.
  • OverflowToDisk: whether to save to a disk.
  • TimeToIdleSeconds: Sets the amount of time (in seconds) an object is allowed to be idle before it expires. Only when theeternal=falseAn optional property used when the object is not permanently valid. The default value is 0, which means that the idle time is infinite.
  • TimeToLiveSeconds: Sets the amount of time (in seconds) an object can live before it expires. The maximum time is between the creation time and the expiration time. Only when theeternal=falseThis is used when the object is not permanent. The default value is 0, which means the object has an infinite lifetime.
  • DiskPersistent: specifies whether to cache vm data during vm restart. The default value is false.
  • DiskSpoolBufferSizeMB: This parameter sets the cache size of the DiskStore (disk cache). The default is 30MB. Each Cache should have its own buffer.
  • DiskExpiryThreadIntervalSeconds: disk failure thread running time interval, the default is 120 seconds.
  • MemoryStoreEvictionPolicy: when maxElementsInMemory limit is reached, Ehcache will be based on the specified strategies to clear the memory. The default policy is LRU (least recently used). You can set it to FIFO (first in, first out) or LFU (less used).
  • ClearOnFlush: specifies whether to flush the memory when the memory quantity reaches the maximum.
  • MemoryStoreEvictionPolicy: Ehcache three clear strategy: FIFO, the first in first out, this is everybody’s most familiar, the first in first out. LFU, Less Frequently Used, is the strategy Used in the example above, which is the least Frequently Used. As mentioned above, cached elements have a hit attribute, and the one with the lowest hit value will be cleared out of the cache. LRU, Least Recently Used, the cached element has a timestamp. When the cache is full and needs to make room for new elements, the element whose timestamp is furthest from the current time will be cleared from the cache.

Import dependence

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

Creating an XML file

<? The 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="false"> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> <cache name="user" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"/> </ehcache>Copy the code

Yml configuration

spring: datasource: url: jdbc:mysql://localhost:3306/test? allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8 driver-class-name: com.mysql.cj.jdbc.Driver username: Root password: root redis: host: 127.0.0.1 port: 6379 jedis: pool: Max wait: -1 # Max idle: 8 # Min idle: 0 cache: ehcache: config: ehcache.xmlCopy the code

For Ehcache, the update method is the same with or without the @cachePUT annotation.

The data format stored in Redis is as follows