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
id | user_id | friend_id |
---|---|---|
1 | 1 | 2 |
2 | 1 |
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.