Spring Boot与缓存(默认缓存 + Redis)

289 阅读16分钟

文章目录


1. 引入

在实际的应用场景中,对于数据库来说,并不是所有的表都会对其使用同等频率的操作,而是对于某些热点信息所在的表操作更加的频繁。如果每次获取表中的信息都需要连接数据库、执行SQL语句和关闭连接等一系列的操作,那么性能的开销将很大。因此,为了减少开销,同时保证查询的速度,常常使用缓存来保存热点数据。当再次查询相同的数据时,不必从数据库中查询,而是直接从缓存中获取,大大的提高了效率。

Spring Boot中缓存都要遵守JSR107规范,它定义了缓存所需的5个核心接口,分别是:

  • CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider
  • CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在CacheManager的上下文中,一个CacheManager仅被一个CachingProvider所拥有
  • Cache:一个类似Map的数据结构并临时存储以Key为索引的值,一个Cache仅被一个CacheManager所拥有
  • Entry:一个存储在Cache中的key-value对
  • Expiry:每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目更改为为过期状态,此时条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置

它们之间的关系如下所示:


在这里插入图片描述



2. Spring缓存抽象

所有满足JSR107规范的缓存都可以被使用,Spring框架为了统一不同类型的缓存定义了了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口,并提供了同样满足规范的注解简化开发。


2.1 Cache接口

Cache接口规范的定义了每种缓存组件的规范,包含缓存的各种操作。Spring中的Cache接口为各种常用的缓存都提供了接口实现,如RedisCache,EhCacheCache , ConcurrentMapCache等。当Spring Boot开启缓存功能,每次调用需要缓存功能的方法时,Spring首先会从缓存中根据指定的参数寻找是否有满足要求的数据,如果有则直接返回;否则就调用方法返回结果,同时将结果保存到缓存中。

因此,当在Spring中使用缓存时,需要考虑以下几点:

  • 什么样的方法需要缓存功能?
  • 如果需要缓存,那么什么样的缓存策略适合?
  • 当需读取数据时,优先考虑从缓存中寻找

2.2 CacheManager接口

CacheManager是–个缓存通用接口抽象类库,它支持各种高速缓存提供者,例如Memcache,Redis,并且有许多先进的功能特性。它的设计目标就是简化程序员对各种复杂缓存场景的处理,通过CacheManager只需要几行的代码就可以支持多层的缓存,从进程内缓存到分布式的缓存。通过CacheManager可以很容易在项目中更改缓存策略,它还提供一些更有价值的特性,例如高速缓存同步,并发更新,事件通知,性能计数器等。


在这里插入图片描述



3. 核心概念


3.1 核心注解

概念说明
Cache缓存接口,定义缓存操作,实现有RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager缓存管理器,管理各种缓存(Cache)组件
@Cacheable针对方法配置,可根据方法的请求参数对其结果进行缓存
@CacheEvict清空缓存
@Cacheput保证方法被调用,又希望结果被缓存
@EnableCaching开启基于注解的缓存
keyGenerator缓存数据时key的生成策略
serialize缓存数据时value序列化序列

3.2 主要参数

这些参数针对于@Cacheable、@CachePut和@CacheEvict 这三个注解

参数描述例子
value缓存的名称,在spring 配置文件中定义,必须指定至少一个`@Cacheable(value=”mycache”) 或@Cacheable(value={”cache1”,”cache2”}
key缓存的key,可以为空,如果指定要按照SpEL 表达式编写,如果不指定,则默认按照方法的所有参数进行组合@Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用SpEL 编写,返回true 或者false,只有为true 才进行缓存/清除缓存,在调用方法之前之后都能判断@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
allEntries(@CacheEvict)是否清空所有缓存内容,默认为false,如果指定为true,则方法调用后将立即清空所有缓存@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation(@CacheEvict)是否在方法执行前就清空,默认为false,如果指定为true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存@CachEvict(value=”testcache”,beforeInvocation=true)
unless(@CachePut、@Cacheable)用于否决缓存,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存@Cacheable(value=”testcache”,unless=”#result == null”)


4. SpEL表达式

名字位置描述例子
methodNameroot object当前被调用的方法名#root.methodName
methodroot object当前被调用的方法#root.method.name
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类#root.targetClass
argsroot object当前被调用的方法的参数列表#root.args[0]
cachesroot object当前方法调用使用的缓存列表#root.caches[0].name
argument nameevaluation context方法参数的名字. 可以直接#参数名,也可以使用#p0或#a0 的形式,0代表参数的索引;#iban、#a0 、#p0
resultevaluation context方法执行后的返回值#result


5. 使用案例


5.1 环境搭建

结合Mybatis对于数据库的操作来演示如何在项目中使用缓存,假设此时的account表如下所示:

mysql> select * from account;
+----+----------+-------+
| id | name     | money |
+----+----------+-------+
|  1 | Forlogen |  1000 |
|  2 | Kobe     |  1000 |
|  3 | James    |  1000 |
+----+----------+-------+
3 rows in set (0.00 sec)

要想要在Spring Boot中使用缓存,首先需引入spring-boot-starter-cache模块:

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

然后在主程序中使用@EnableCaching开启基于注解的缓存。

为了使用Mybaits进行数据持久化操作,这里还需要配置数据源,另外使Mybatis支持驼峰命名法:

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/sql_store?serverTimezone=GMT
    driver-class-name: com.mysql.cj.jdbc.Driver


mybatis:
  configuration:
    map-underscore-to-camel-case: true

编写表对应的实体类Account,注意这里要实现Serializable 接口,保证类对象可以进行序列化和反序列化:

@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Account implements Serializable {

    @Getter
    @Setter
    private Integer id;

    @Getter
    @Setter
    private String name;

    @Getter
    @Setter
    private Float money;
}

5.2 无缓存策略

编写持久层接口AccountMapper,接口中包含查询所有和根据ID查询两个方法:

@Mapper
public interface AccountMapper {

    @Select("select * from account")
    public List<Account> findAll();

    @Select("select * from account where id=#{id}")
    public Account findById(@Param("id")Integer id);
}

同时在被@SpringBootApplication注解标识的启动类中使用@MapperScan("xxx")指定要扫描的被@Mapper注解标识的类所在的包。

编写业务层实现类,并实现AccountMapper对象的自动注入:

@Service
public class AccountService {

    @Autowired
    AccountMapper accountMapper;

    public List<Account> findALl(){
        System.out.println("service findAll...");
        List<Account> all = accountMapper.findAll();
        return all;
    }

    public Account findById(Integer id){
        System.out.println("service findById"+id);
        Account account = accountMapper.findById(id);

        return account;
    }
}

最后,编写表现层的Controller

@RestController
public class AccountController {

    @Autowired
    AccountService accountService;

    @GetMapping("/account")
    public List<Account> testFindAll(){
        List<Account> aLl = accountService.findALl();
        return aLl;
    }

    @GetMapping("/account/{id}")
    public Account testFindById(@PathVariable("id") Integer id){
        Account account = accountService.findById(id);
        return account;
    }
}

执行主程序,并在浏览器中通过localhaost:8080/account查询所有,此时可以得到表中的信息:

[{"id":1,"name":"Forlogen","money":1000.0},{"id":2,"name":"Kobe","money":1000.0},{"id":3,"name":"James","money":1000.0}]

并且执行多次查询,控制台每一次都会输出service findAll...,表示每一次的查询操作都要执行SQL操作来查询数据库获取结果。通过localhaost:8080/account/1根据ID查询同样会在每次查询时执行SQL语句,这样显然是不好的选择。


5.3 使用默认缓存

在环境搭建中已经导入缓存所需的依赖,但上面并没有使用缓存,所以每次查询都需要访问数据库。使用缓存首先需在主程序上使用@EnableCaching开启缓存

@SpringBootApplication
@MapperScan("dyliang.mapper")
@EnableCaching
public class DyliangApplication {

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

然后对service层中要使用缓存的方法添加注解,这里首先使用@Cacheable

@Cacheable(value = "account", key = "root.args[0]")
public List<Account> findALl(){
    System.out.println("service findAll...");
    List<Account> all = accountMapper.findAll();
    return all;
}

执行主程序发动请求,注意观察控制台的输出。首先可以看到,Spring Boot默认匹配的是SimpleCacheConfiguration

SimpleCacheConfiguration matched:
- Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type (CacheCondition)
- @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans (OnBeanCondition)

第一次执行查询,控制台输出:

service findAll...
2020-06-20 21:52:50.355  INFO 5460 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-06-20 21:52:51.015  INFO 5460 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.

然后多次执行相同的查询继续观察控制台输出,可以发现此时service findAll...只会在首次执行查询时输出,后续相同的操作并不会输出该信息,说明缓存已经生效。

对根据ID查询的方法同样开启缓存

@Cacheable(value = "account", key = "#id", condition = "#id>1")
public Account findById(Integer id){
    System.out.println("service findById"+id);
    Account account = accountMapper.findById(id);
    return account;
}

执行主程序发送请求,发现此时控制台在每一次查询时都会输出service findById1,难道缓存没有生效嘛?其实,是因为在@Cacheable中配置了condition属性,它表示:只有在ID大于1时才缓存结果。如果此时查询ID为2,可以看到,只会在首次查询是看到service findById1,说明缓存仍然是生效的,而且condition属性也起到了作用。

@Cacheable的源码定义如下所示:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
    @AliasFor("cacheNames")
    String[] value() default {};

    @AliasFor("value")
    String[] cacheNames() default {};

    String key() default "";

    String keyGenerator() default "";

    String cacheManager() default "";

    String cacheResolver() default "";

    String condition() default "";

    String unless() default "";

    boolean sync() default false;
}

最后来分析一下使用到的属性,以及其他未用到的该如何使用:

  • value/cacheNames:用于指定缓存组件的名字,String类型的数组,表示可以指定多个缓存

  • key:用于指定编写缓存数据时所用的key,默认时方法的参数值,也可以通过SpEL表达式指定

  • keyGenerator:key的生成器,可以指定自定义的key的生成器的组件id,但是它和key只能使用其中一个。

    其中的keyGenerator可以通过实现相应的KeyGenerator接口自定义,并将其注册到Spring Boot的ioc容器中。KeyGenerator在Spring Boot中定义为一个函数式接口,接口的实现只需要重写其中的generate()即可。例如,下面就是一个简单的自定义keyGenerator:

    @Configuration
    public class MyCacheConfig {
    
        @Bean("myKeyGenerator")
        public KeyGenerator keyGenerator(){
            return new KeyGenerator(){
    
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    return method.getName()+"["+ Arrays.asList(params).toString()+"]";
                }
            };
        }
    }
    
  • cacheManager:指定缓存管理器,或者cacheResolver指定获取解析器

  • condition:指定符合条件的情况下才缓存,例如上面的condition = #id>1表示当输入的ID大于1时才使用缓存

  • unless:否定缓存,例如可以配置unless = "#id==1"表示如果传入的ID为1则不会使用缓存

  • sync:是否使用异步模式

为了实验前面提到的@CachePut@CacheEvict@Caching,首先在AccountMapper中添加一些方法:

@Mapper
public interface AccountMapper {

    @Select("select * from account")
    public List<Account> findAll();

    @Select("select * from account where id=#{id}")
    public Account findById(Integer id);

    @Select("select * from account where name=#{name}")
    public Account findByName(String name);

    @Insert("insert into account(name,money) values (#{name},#{money})")
    public int insertAccount(Account account);

    @Update("update account set name=#{name} where id=#{id}")
    public int updateAccount(Account account);

    @Delete("delete from account where id=#{id}")
    public void deleteAccount(Integer id);
}

更新Service层的方法:

@Service
public class AccountService {   
    @CachePut(value = "account", key = "#result.id")
    public Account updateAccount(Account account){
        accountMapper.updateAccount(account);
        return account;

    }

    @CacheEvict(value = "account", key = "#id", beforeInvocation = true)
    public void deleteAccount(Integer id){
        accountMapper.deleteAccount(id);
    }

    @Caching(
        cacheable = {@Cacheable(value = "account", key = "#name")},
        put = {
            @CachePut(value = "account", key = "#result.id"),
            @CachePut(value = "account", key = "#result.name")
        }
    )
    public Account findByName(String name){
        Account account = accountMapper.findByName(name);
        return account;
    }
}

更新Controller:

@RestController
public class AccountController {

    @Autowired
    AccountService accountService;

    @ResponseBody
    @GetMapping("/delAccount/{id}")
    public String testDeleteAccount(@PathVariable("id") Integer id){
        accountService.deleteAccount(id);
        return "success";
    }

    @GetMapping("/account/name/{name}")
    public Account testFindByName(@PathVariable("name") String name){
        Account byName = accountService.findByName(name);
        return byName;
    }

    @GetMapping("/account/update")
    public Account testUpdateAccount(Account account){
        Account account1 = accountService.updateAccount(account);
        return account1;
    }
}

最后执行执行主程序发送不同的请求,都可以在控制台和数据库中表的更新来看到不同注解的功能。

总结:

  • @CachePut:既调用方法,又更新缓存数据,而且是同步更新。它首先调用目标方法,然后将结果缓存起来,但缓存不一定成功,所以说是希望缓存结果

    public @interface CachePut {
        @AliasFor("cacheNames")
        String[] value() default {};
    
        @AliasFor("value")
        String[] cacheNames() default {};
    
        String key() default "";
    
        String keyGenerator() default "";
    
        String cacheManager() default "";
    
        String cacheResolver() default "";
    
        String condition() default "";
    
        String unless() default "";
    }
    
  • @CacheEvict:缓存清除,其中属性:

    • key:指定要清除的数据

    • allEntries = true:指定清除这个缓存中所有的数据

    • beforeInvocation:缓存的清除是否在方法之前执行

      • false:缓存清除操作是在方法执行之后执行,如果出现异常缓存就不会清除
      • true:缓存清除操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
    public @interface CacheEvict {
        @AliasFor("cacheNames")
        String[] value() default {};
    
        @AliasFor("value")
        String[] cacheNames() default {};
    
        String key() default "";
    
        String keyGenerator() default "";
    
        String cacheManager() default "";
    
        String cacheResolver() default "";
    
        String condition() default "";
    
        boolean allEntries() default false;
    
        boolean beforeInvocation() default false;
    }
    
  • @Caching:定义复杂的缓存规则

    public @interface Caching {
        Cacheable[] cacheable() default {};
    
        CachePut[] put() default {};
    
        CacheEvict[] evict() default {};
    }
    

另外还可以在类上使用@CacheConfig来抽取缓存公共的配置,如value/cacheNamecacheManager等。

@CacheConfig(cacheNames="xxx" ,cacheManager = "xxx")


6. 整合Redis


6.1 概述

Redis(REmote DIctionary Server)是一个由Salvatore Sanfilippo写的key-value存储系统,它是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和有序集合(sorted sets)等类型。


6.2 安装Redis

这里同样在云服务器上通过docker安装Redis:

  • 拉取镜像:docker pull redis
  • 映射端口运行Redis:docker run -d -p 6379:6379 --name redis redis的镜像名
  • 查看:docker ps

在这里插入图片描述

注意:为了可以实现远程客户端连接,还需要在服务器的安全组中添加规则:
在这里插入图片描述

最后使用Redis客户端测试连接,如果出现如下界面,表示连接成功。


在这里插入图片描述


6.3 使用案例

表现层、持久层都和前面一样,为了方便演示,这里只使用查询所有和根据ID查询两个方法,如下所示:

@Mapper
public interface AccountMapper {

    @Select("select * from account")
    public List<Account> findAll();

    @Select("select * from account where id=#{id}")
    public Account findById(Integer id);
}
@RestController
public class AccountController {

    @Autowired
    AccountService accountService;

    @GetMapping("/account")
    public List<Account> testFindAll(){
        List<Account> aLl = accountService.findALl();
        return aLl;
    }

    @ResponseBody
    @GetMapping("/account/{id}")
    public Account testFindById(@PathVariable("id") Integer id){
        Account account = accountService.findById(id);
        return account;
    }
}

Spring Boot要整合Redis作为缓存,首先需引入依赖:

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

Spring Boot2.x中使用的是lettuce操作Redis,Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问。

Springboot 1.x整合Spring-data-redis底层用的是jedis,jedis在多线程环境下是非线程安全的,使用了jedis pool连接池,为每个Jedis实例增加物理连接。

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <scope>compile</scope>
</dependency>

运行主程序,从控制台输出可以看出此时的Redis缓存已经生效。

RedisCacheConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.data.redis.connection.RedisConnectionFactory' (OnClassCondition)
- Cache org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration automatic cache type (CacheCondition)

发送请求http://localhost:8080/account执行查询所有,浏览器可以得到正确数据:

[{"id":1,"name":"Forlogen","money":1000.0},{"id":2,"name":"Kobe","money":1000.0},{"id":3,"name":"James","money":1000.0}]

查看Redis,可以看到此时数据已经成功的保存在了Redis中,但是保存的是序列化后的结果,所以结果并不具有可读性。


在这里插入图片描述

执行根据ID查询的请求同样可以将结果缓存到Redis中。


在这里插入图片描述

并且再次发送同样的请求,从控制台可以看出并没有访问数据库,因此Redis缓存已经生效。但如果使得保存到Redis中的结果具有可读性,例如转换为json格式?这就需要自定义RedisCacheManager和RedisTemplate。


6.4 自定义缓存数据格式

为了使得Redis中缓存的数据具有可读性,通常将其转化为json格式在进行缓存,同样反序列化使用的也是json格式的缓存数据。实现自定义缓存数据格式需要自定义Redis的RedisCacheManagerRedisTemplate,并使用Jackson2JsonRedisSerializer实现序列化数据转换为json格式,代码如下所示:

@Configuration
public class MyRedisConfig {

    @Resource
    //lettuce客户端连接工厂
    private LettuceConnectionFactory lettuceConnectionFactory;
    // 日志
    private Logger logger= (Logger) LoggerFactory.getLogger(MyRedisConfig.class);
    // json序列化器
    private Jackson2JsonRedisSerializer<Account> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Account.class);
    //过期时间1天
    private Duration timeToLive = Duration.ofDays(1);

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // Redis缓存配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(this.timeToLive)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
                .disableCachingNullValues();

        //缓存配置map
        Map<String,RedisCacheConfiguration> cacheConfigurationMap=new HashMap<>();

        //自定义缓存名,后面使用的@Cacheable的CacheName
        cacheConfigurationMap.put("account",config);
        cacheConfigurationMap.put("default",config);

        //根据redis缓存配置和reid连接工厂生成redis缓存管理器
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .withInitialCacheConfigurations(cacheConfigurationMap)
                .build();
        logger.debug("自定义RedisCacheManager加载完成");
        return redisCacheManager;
    }

    //redisTemplate模板提供给其他类对redis数据库进行操作
    @Bean(name = "redisTemplate")
    public RedisTemplate<String,Account> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String,Account> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());
        logger.debug("自定义RedisTemplate加载完成");
        return redisTemplate;
    }

    //redis键序列化使用StrngRedisSerializer
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    //redis值序列化使用json序列化器
    private RedisSerializer<Object> valueSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

Springboot-redis

编写好自己的RedisCacheManager和RedisTemplate并将其通过@Bean添加到容器中后,再次运行程序并发送请求。在浏览器中可以看到仍然可以得到表中的信息:

[{"id":1,"name":"Forlogen","money":1000.0},{"id":2,"name":"Kobe","money":1000.0},{"id":3,"name":"James","money":1000.0}]

再去查看Redis中保存的结果,可以看到此时结果就以json格式保存到缓存中。


在这里插入图片描述
执行根据ID查询的请求同样可以将结果以json格式保存到缓存中。


在这里插入图片描述


6.5 Redis常用操作

Redis常见的五大数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)。Spring Boot中提供了stringRedisTemplate用于操作各种数据类型的数据,相应的方法有:

  • stringRedisTemplate.opsForValue():用于操作String类型数据
  • stringRedisTemplate.opsForList():用于操作List
  • stringRedisTemplate.opsForSet():用于操作Set
  • stringRedisTemplate.opsForHash():用于操作Hash
  • stringRedisTemplate.opsForZSet():用于操作ZSet

下面通过一个简单的例子来看一下如何使用这些函数:

@RunWith(SpringRunner.class)
@SpringBootTest
class DyliangApplicationTests {

    @Autowired
    StringRedisTemplate stringRedisTemplate;  //操作k-v都是字符串的

    @Test
    public void testRedis(){
        //给redis中保存数据
        stringRedisTemplate.opsForValue().append("msg","hello");
		String msg = stringRedisTemplate.opsForValue().get("msg");
		System.out.println(msg);

		stringRedisTemplate.opsForList().leftPush("mylist","1");
		stringRedisTemplate.opsForList().leftPush("mylist","2");
    }
}

单元测试执行成功,然后去Redis中查看可以看到关于String和List类型的数据都成功保存在了缓存中。


在这里插入图片描述



7. 更多

更多操作可查看:Redis中文官方网站