springbootCache and Redis caching mechanism

brief introduction

I want to develop a social networking site. Storing friends list on social networking sites has become a big problem in the development process.

If I want to save every user's friend information, how should I save it?

The user's id corresponds to the friend's id. such a message is a friend relationship map. This relationship table may be as long as the following

iduser_idfriend_id
112
21

Friends of a user_ ID record. User 1 and user 2 are friends with each other. Although such records are redundant, they have to be recorded. But this has great disadvantages.

Suppose I have 200 users and each user has 300 friends, then such a table will have 200 * 300 = 60000 records.

Every time users log in, they will read the records in the database again. Think about it like this, isn't it terrible!!

In order to solve this problem, we can put the content read by users in the cache every time, so that when users log out and enter again, they can directly read the content in the cache, reducing the pressure on our database.

Moreover, the cache has the advantage of fast reading speed, which can better optimize the user experience.

SpringbootCache

The built-in cache in spring, especially in springboot, provides annotations to facilitate our use. In order to learn the cache system of springboot well, we must first understand several specifications defined by java for cache

JSR-107 specification

Java Caching defines five core interfaces: cacheingprovider, CacheManager, cache, entry and expiration.

  • CachingProvider defines the creation, configuration, acquisition, management and control of multiple cachemanagers. An application can access multiple cacheingproviders at runtime.
  • CacheManager defines the creation, configuration, acquisition, management and control of multiple uniquely named caches, which exist in the context of CacheManager. A CacheManager is owned by only one cacheingprovider.
  • Cache is a Map like data structure and temporarily stores values indexed by Key. A cache is owned by only one CacheManager.
  • Entry is a key value pair stored in the Cache.
  • Expiry each entry stored in the cache has a defined validity period. Once this time is exceeded, the entry is expired. Once expired, entries will not be accessible, updated, or deleted. Cache validity can be set through ExpiryPolicy.

This figure shows all the interfaces defined by JSR-107 specification, but there is no specific implementation

An application can have multiple caching providers. A cache provider can obtain multiple cache managers. A cache manager manages different caches. In the cache, there are cache key value pairs (entries), and each entry has an expiration date. The relationship between cache manager and cache is somewhat similar to the relationship between connection pool and connection in database.

Quoted from the above specification Blog

Important concepts

The Cache interface defines Cache operations. Implementations include RedisCache, EhCacheCache, ConcurrentMapCache, etc
CacheManager is a cache manager that manages various cache components
@Cacheable is mainly used for method configuration. It can cache the results according to the request parameters of the method
@Empty cache evict
@CachePut ensures that the method is called and the result is cached.
@EnableCaching enables annotation based caching
key generation strategy when keyGenerator caches data
serialize value serialization policy when caching data

The default cache of springboot is ConcurrentMapCache

How cache works

1. Auto configuration class:

CacheAutoConfiguration: the CacheConfigurationImportSelector imported through CacheAutoConfiguration will add the full class names of some cached configuration classes to the array
  2. Configuration class of cache

org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
  org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
  org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
  org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
  org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
  org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
  org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
  org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
  org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
  org. springframework. boot. autoconfigure. cache. Simplecacheconfiguration (used by default)
  org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
  3. Configuration class in effect by default: SimpleCacheConfiguration
  4. SimpleCacheConfiguration registers a CacheManager in the container: ConcurrentMapCacheManager

@Configuration
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {
    private final CacheProperties cacheProperties;
    private final CacheManagerCustomizers customizerInvoker;

    SimpleCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) {
        this.cacheProperties = cacheProperties;
        this.customizerInvoker = customizerInvoker;
    }

    @Bean
    public ConcurrentMapCacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        List<String> cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }

        return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
    }
}

5. The concurrent mapcachemanager allows you to obtain and create cache components of the concurrent mapcache type: the function of the concurrent mapcache is to save data in the concurrent map
  6. Operation process of @ Cacheable:
   ① before the method runs, first query the Cache (Cache component) and obtain it according to the name specified by cacheNames (CacheManager obtains the corresponding Cache first, and if there is no Cache component for the first time, it will be created automatically)
   ② go to the Cache to find the contents of the Cache. The key used is the parameter of the method by default:
    key is generated by keyGenerator by default, and SimpleKeyGenerator is used by default
    default policy for generating keys by SimpleKeyGenerator:
     if there are no parameters: key = new SimpleKey();
     if there is a parameter: key = parameter value
     if there are multiple parameters: key = new SimpleKey(params);
   ③ call the target method if the cache is not found
   ④ put the result returned by the target method into the cache
  summary: @ Cacheable marked method will check whether there is this data in the cache before execution. By default, query the cache according to the value of the parameter key. If not, run the method and put the result into the cache. When calling later, directly use the data in the cache.
  core:
  1 ️⃣ Use the CacheManager(ConcurrentMapCacheManager) to get the Cache(ConcurrentMapCache) component by name
  2 ️⃣ Keys are generated using keyGenerator, and SimpleKeyGenerator is used by default

Practical operation

idea select dependency table

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
		<!--use Mybatis-plus Operation database-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>
		<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
<!--    Do not import redis The bag is because redis It can also be used as a cache. When this package is introduced, springboot Will automatically redis As cacheManager,This will
    Cannot proceed ConcurrentMapCacheManager So comment it out first. If you want to use it redis As a cache, and then unseal redis package

        <dependency>
        <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
       </dependency>
    -->

Import the above dependencies.

Product class in pojo package

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
//    The id should be consistent with that in the database
    @TableId("productid")
    private String productId;
    private String category;
    private String name;
    private String descn;
}

ProductMapper in dao package

@Mapper
@Repository
public interface ProductMapper extends BaseMapper<Product> {

}

ProductServiceImpl in service package

@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    private ProductMapper productMapper;

    private static Integer count=0;

    @Override
    /*
    The reason for specifying the cacheManager here is that the default implementation of cache is SimpleCacheConfiguration, which is registered
    CacheManager The bean name is cacheManager
     */
    @Cacheable(cacheNames = {"emp"},cacheManager = "cacheManager")
    public List<Product> guProduct() {
        System.out.println("This is the second"+(++count)+"This method is called once");
        return productMapper.selectList(null);
    }


    @Override
    @Cacheable("emp")
    public Product getProductById(String id){
        return productMapper.selectById(id);
    }

    @Override
    @CacheEvict(cacheNames = {"emp"},cacheManager = "cacheManager",allEntries = true)
    public int guProduct(Product product){
        System.out.println("Because you updated the data, I will delete it emp All data in cache");
        return productMapper.updateById(product);
    }
}

Open the log of mybatis plus in application In properties

#Open the log of mybatis plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

ProductController in controller package

@RestController
public class ProductController {

    @Autowired
    private ProductService productService;

    @RequestMapping("/getAll")
    @SneakyThrows
    public String getAllProduct(){
        ObjectMapper objectMapper = new ObjectMapper();
        List<Product> products = productService.guProduct();
        return objectMapper.writeValueAsString(products);
    }

    @RequestMapping("/update/{id}")
    @SneakyThrows
    public Integer update(@PathVariable("id")String id){
        Product product = new Product(id,"test","test","test");
        int i = productService.guProduct(product);
        return i;
    }
}

Add this comment to the main startup class

@SpringBootApplication
@EnableCaching //Enable annotation based caching
public class SpringcacheApplication {

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

}

start-up

When entering in the browser

localhost:8080/getAll

Display in Sqlyog

Display on console

Then, if you keep refreshing, you will find that the count is still 1, indicating that the results in the database have entered the cache. This can be tested by yourself

When I type in the browser

localhost:8080/update/2005

console output

Then enter again

localhost:8080/getAll

Console and page output

Due to the clearing of the cache, the database can only be read again. There is also a CachePut annotation, which will integrate the above operations. That is, when an update method is executed, the cache will be updated automatically, but the cache name and key value of the two methods must be the same, but they are not commonly used in development. Interested readers can study it by themselves.

RedisCache

On the basis of SpringbootCache, we import the dependency of redis

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

At this time, springboot will automatically register Redis as a CacheManager, and the CacheManager in @ Cacheable annotation will automatically expire. We need to start a new Redis server and configure it.

In application Enter the following two lines of configuration in properties

#The default host address is localhost, which can be configured without configuration
spring.redis.host= localhost
#The default port number is 6379, which can not be configured
spring.redis.port= 6379

Start a redis server service locally

redis serialization

When redis is used to store an object, the object must be Serializable (implement the Serializable interface), otherwise an error will be reported

Add in the original product class

@Data
@AllArgsConstructor
@NoArgsConstructor
//This is very important. Otherwise, it cannot be serialized and prduct cannot be converted into json format and stored in redis
public class Product implements Serializable {
//    The id should be consistent with that in the database
    @TableId("productid")
    private String productId;
    private String category;
    private String name;
    private String descn;
}

By default, the object serialization method of JDK is adopted by SpringBoot. We can switch to using JSON format for object serialization. At this time, we need to customize the serialization rules (of course, we can also use JSON tool to convert the object into JSON format and then save it to redis, so there is no need to customize serialization).

We can make a comparison between JDK and json format serialization

JDK serialization [default]

JDK serialization method after inheriting objects from Serializable interface

We can find that the name of the key is automatically generated by Keygenerator, and the result of serialization of "emp::SimpleKey []" is a Chinese character set. There is no json format, which is difficult to observe.

JSON serialization

After configuring the above redis serialization config, you also need to customize RedisCacheManager

Using the RedisCacheManager provided by SpringBoot, the JDK serialization mechanism is adopted when serializing data. We can change the data serialization to JSON mechanism by customizing the CacheManager

@Configuration
public class MyRedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(){
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        configuration = configuration.serializeValuesWith
                (RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .entryTtl(Duration.ofDays(30));
        return configuration;
    }

}

Once configured, we will serialize in json format.

At this point, the SerialIzable interface can be serialized even if it does not inherit

Save format in redis

Save in json format.

In addition, because the redis server and tomcat server are separated, even if the tomcat service is closed, the cache will not disappear. At this time, all the data is saved in the redis server.

Keywords: Java Redis Cache

Added by Magena Cadmar on Thu, 10 Feb 2022 23:32:20 +0200