web123456

Integration of Spring-cache and redis in Springboot

It was also when I was integrating Redis and I accidentally discovered spring-cache. This is also a good framework, similar to the use of spring transactions. Just add some annotation methods to dynamically operate the cache and reduce the operation of the code. If these annotations do not meet the needs of the project, we can also refer to the implementation idea of ​​spring-cache and use AOP proxy + cache operations to manage the use of cache.
In this example, I am using redis. Of course, because of the existence of spring-cache, we can integrate a variety of caching technologies, such as Ecache, Mamercache, etc.
Let’s take a look at the specific operation of springcache!
Attached with official documentation:
/spring/docs/current/spring-framework-reference/html/

Integration of spring-cache in redis

My code project was used in the previous project, so I can read it in combination with the previous blog
/u011521890/article/details/78070773
Or just look for github, welcome to star
/hpulzl/book_recommend

The cache configuration is as follows

  • Add annotation on RedisCacheConfig
@EnableCaching // Add this annotation to support cache annotation
  • Create RedisCacheManager
 /**
      * Set up RedisCacheManager
      * Use cache annotations to manage redis cache
      *
      * @return
     */
    @Bean
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate());
        return redisCacheManager;
    }
  • Custom cached keys
 /**
      * Custom generated redis-key
      *
      * @return
     */
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                StringBuilder sb = new StringBuilder();
                (().getName()).append(".");
                (()).append(".");
                for (Object obj : objects) {
                    (());
                }
                ("keyGenerator=" + ());
                return ();
            }
        };
    }

Add the above code to RedisCacheConfig, and you can use the annotation of springcache. The following describes how to use springcache annotations

Spring cache combined with redis cache

Understanding of springCache concept

springCache supports transparent addition of cache to applications, similar to transaction processing and does not require complex code support.

The main ways to use cache include the following two aspects
1. The cached statement needs to be properly cached according to project requirements.
2. Cache configuration method, select the required cache support, such as Ecache, redis, memercache, etc.

Introduction to cached annotations

@Cacheable triggers the cache entry

@CacheEvict Triggers cache removal

@CacahePut Update cache

@Caching Group multiple cache operations

@CacheConfig class-level cache annotation, allowing shared cache names

@CacheConfig

This annotation is a cache classifier, which is a class-level annotation method. We can use it like this.
In this way, all cache annotations of UseCacheRedisService, such as @Cacheable's value, will be user.

@CacheConfig(cacheNames = "user")
@Service
public class UseCacheRedisService {}

The cache of redis is shown as follows

127.0.0.1:6379> keys *
1) "user~keys"
2) "user_1"
127.0.0.1:6379> get user~keys
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> type user~keys
zset
127.0.0.1:6379> zrange user~keys 0 10
1) "user_1"

We noticed that the generated user~keys is a zset type key. If you use get, WRONGTYPE Operation against a key holding the wrong kind of value. This question has tricked me for a long time

@Cacheable

It is generally used for query operations and query cache according to key.


  1. If the key does not exist, query db and update the result to cache.
  2. If the key exists, query the cached data directly.


Query example, when the first query does not exist in redis, the data will be queried from db and the returned result will be inserted into redis.

@Cacheable
    public List<User> selectAllUser(){
        return ();
    }

Call method.

@Test
    public void selectTest(){
        System.out.println("=============== First call===========");
        List<User> list = ();
        System.out.println("============== The second call==========");
        List<User> list2 = ();
        for (User u : list2){
            System.out.println("u = " + u);
        }
    }

Print the results, you can also try it
A statement that only outputs a SQL query once, indicating that the second query was found from Redis.

============== First call=========
keyGenerator=.
keyGenerator=.
DEBUG [main] - ==>  Preparing: SELECT id,name,sex,age,password,account FROM user 
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 1
============= The second call=========
keyGenerator=.
u = User{id=1, name='fsdfds', sex='fdsfg', age=24, password='gfdsg', account='gfds'}

Results in redis
We can see that it already exists in redis
.Recorded.
Such a long string of characters is generated based on the custom key value.

127.0.0.1:6379> keys *
1) "user~keys"
2) "."
3) "user_1"
127.0.0.1:6379> get .
"[\"\",[[\"\",{\"id\":1,\"name\":\"fsdfds\",\"sex\":\"fdsfg\",\"age\":24,\"password\":\"gfdsg\",\"account\":\"gfds\"}]]]"

@CachePut

Generally used for update and insert operations, db is requested every time
Go to redis via key.
1. If the key exists, update the content
2. If the key does not exist, insert the content.

 /**
      * Insert operation of a single user object, using user+id
      * @param user
     * @return
     */
    @CachePut(key = "\"user_\" + #")
    public User saveUser(User user){
        (user);
        return user;
    }

Results in redis
One more record user_2

127.0.0.1:6379> keys *
1) "user~keys"
2) "user_2"
3) "."
4) "user_1"
127.0.0.1:6379> get user_2
"[\"\",{\"id\":2,\"name\":\"fsdfds\",\"sex\":\"fdsfg\",\"age\":24,\"password\":\"gfdsg\",\"account\":\"gfds\"}]"

@CacheEvict

Delete the cached data according to the key. allEntries=true means deleting all data in the cache.

 @CacheEvict(key = "\"user_\" + #id")
    public void deleteById(Integer id){
        (id);
    }

Test Method

 @Test
    public void deleteTest(){
        (1);
    }

Results in redis
user_1 has been removed.

127.0.0.1:6379> keys *
1) "user~keys"
2) "user_2"
3) "."

Test the situation when allEntries=true.

 @Test
    public void deleteAllTest(){
        ();
    }
 @CacheEvict(allEntries = true)
    public void deleteAll(){
        ();
    }

Results in redis
All data in redis has been cleared

127.0.0.1:6379> keys *
(empty list or set)

@Caching

It can be seen from the attribute value of the annotation that this annotation integrates other annotation methods. We can customize the annotation according to our needs and apply the previous three annotations together.

public @interface Caching {
    Cacheable[] cacheable() default {};

    CachePut[] put() default {};

    CacheEvict[] evict() default {};
}

Examples of use are as follows

 @Caching(
            put = {
                    @CachePut(value = "user", key = "\"user_\" + #"),
                    @CachePut(value = "user", key = "#"),
                    @CachePut(value = "user", key = "#")
            }
    )
    public User saveUserByCaching(User user){
        (user);
        return user;
    }
 @Test
    public void saveUserByCachingTest(){
        User user = new User();
        ("dkjd");
        ("dsjkf");
        (user);
    }

Execution results in redis
Add three keys at a time

127.0.0.1:6379> keys *
1) "user~keys"
2) "dsjkf"
3) "dkjd"
4) "user_3"

Combined with @Caching, you can also set custom annotations

Custom annotations

@Caching(
        put = {
                @CachePut(value = "user", key = "\"user_\" + #"),
                @CachePut(value = "user", key = "#"),
                @CachePut(value = "user", key = "#")
        }
)
@Target({, })
@Retention()
@Inherited
@Documented
public @interface SaveUserInfo {

}

Use as follows

@SaveUserInfo
    public User saveUserByInfo(User user){
        (user);
        return user;
    }

test

@Test
    public void saveUserByInfoTest(){
        User user = new User();
        ("haha");
        ("hhhcc");
        (user);
    }

Redis results

127.0.0.1:6379> keys *
1) "user_4"
2) "dsjkf"
3) "dkjd"
4) "user~keys"
5) "haha"
6) "hhhcc"
7) "user_3"

Through the above examples, we can basically understand the use of springcache, and of course there are more complex operations. I will just briefly introduce it here. There are still some shortcomings in the application of actual projects. But it shouldn't be too difficult to have this foundation. At the same time, if you have time, you can study the implementation principle of spring-cache. It is based on AOP implementation, which is also where we learn in the project.

Two problems encountered

WRONGTYPE Operation against a key holding the wrong kind of value

This is caused by inconsistent types mentioned above and improper use of the redis command. Therefore, when looking for the value of redis, you need to know the type of key.

type key

Invalid argument(s)

It's still a real error in Redis. This is a bit confusing. When getting it, you must add "(quotation marks).

127.0.0.1:6379> keys *
1) "user_4"
2) "{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}"
3) "dsjkf"
4) "dkjd"
5) "user~keys"
6) "haha"
7) "hhhcc"
8) "user_3"
127.0.0.1:6379> get .UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}
Invalid argument(s)
127.0.0.1:6379> type .UseCacheRedisService.saveUserByErr.User{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}
Invalid argument(s)
127.0.0.1:6379> get "{id=5, name='fsdsg', sex='vcxvx', age=24, password='vcxvcxc', account='vxcvxc'}"
"[\".User\",{\"id\":5,\"name\":\"fsdsg\",\"sex\":\"vcxvx\",\"age\":24,\"password\":\"vcxvcxc\",\"account\":\"vxcvxc\"}]"