Spring Boot (四十九)——springcache整合Redis实现缓存


Spring3.1中开始引入了令人激动的Cache,在Spring Boot中,可以非常方便的使用Redis来作为Cache的实现,进而实现数据的缓存。

1、工程创建

首先创建一个Spring Boot工程,注意创建的时候需要引入三个依赖,web、cache以及redis及spring security,如下图:
在这里插入图片描述

 <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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2、spring cache注解

spring boot cache 提供了一些注解方便做cache应用。

(1)@CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置
(2)@Cacheable:主要方法返回值加入缓存。同时在查询时,会先从缓存中取,若不存在才再发起对数据库的访问。
(3)@CachePut:配置于函数上,能够根据参数定义条件进行缓存,与@Cacheable不同的是,每次回真实调用函数,所以主要用于数据新增和修改操作上。
(4)@CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除对应数据
(5)@Caching:配置于函数上,组合多个Cache注解使用。

1、@Cacheable

@Cacheable 的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存

  • value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
  • key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档
  • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA用户就不会被缓存,读者可自行实验尝试。
  • unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的- 判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
  • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的
  • cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
  • cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。

作用和配置方法
在这里插入图片描述

2、@CachePut

@CachePut 的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用

作用和配置方法
在这里插入图片描述

3、@CacheEvict

@CachEvict 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空

作用和配置方法
在这里插入图片描述

3、基本配置

工程创建好之后,首先需要简单配置一下Redis,Redis的基本信息,另外,这里要用到Cache,因此还需要稍微配置一下Cache,如下:

spring.redis.host=192.168.31.128
spring.redis.port=6379
spring.redis.database=0

spring.cache.cache-names=c1

简单起见,这里我只是配置了Redis的端口和地址,然后给缓存取了一个名字,这个名字在后文会用到。

另外,还需要在配置类上添加如下代码,表示开启缓存:

@SpringBootApplication
@EnableCaching
public class CacheRedisApplication {

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

}

完成了这些配置之后,Spring Boot就会自动帮我们在后台配置一个RedisCacheManager,相关的配置是在org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration类中完成的。部分源码如下:

@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

	@Bean
	public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory,
			ResourceLoader resourceLoader) {
		RedisCacheManagerBuilder builder = RedisCacheManager
				.builder(redisConnectionFactory)
				.cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
		List<String> cacheNames = this.cacheProperties.getCacheNames();
		if (!cacheNames.isEmpty()) {
			builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
		}
		return this.customizerInvoker.customize(builder.build());
	}
}

看类上的注解,发现在万事俱备的情况下,系统会自动提供一个RedisCacheManager的Bean,这个RedisCacheManager间接实现了Spring中的Cache接口,有了这个Bean,我们就可以直接使用Spring中的缓存注解和接口了,而缓存数据则会被自动存储到Redis上。在单机的Redis中,这个Bean系统会自动提供,如果是Redis集群,这个Bean需要开发者来提供。

4、缓存使用示例

首先准备一个bean,注意实体类要序列号:

public class User implements Serializable {
    private int id;
    private String name;
    private String address;

创建一个Service,定义一个通过id查询User的方法,这里我们就不连接数据库了,在方法上加一个注解@Cacheable启用缓存,cacheName就是在配置文件中配置的缓存名称,默认情况下方法的参数就是缓存的key,方法的返回值就是参数的value,如果调用这个方法但是每次传的参数都是一样的,那么这个方法将不再执行,直接返回方法的返回值。

@Service
public class UserService {
    @Cacheable(cacheNames = "c1")
    public User getUserById(int id){
        System.out.println("getUserById>>>" + id);
        User user = new User();
        user.setId(id);
        user.setName("macay");
        return  user;
    }
}

下面我们运行一下测试方法看下:

@SpringBootTest
public class UserServiceTest {
    @Autowired
    UserService userService;

    @Test
    public void getUserById() {
        User user1 = userService.getUserById(1);
        System.out.println(user1);
        User user2 = userService.getUserById(1);
        System.out.println(user2);
    }
}

在这里插入图片描述
可以看到,getUserById(1)调用了两次,这个方法只执行了一次,结果打印了两次,而且结果是相同的,原因也很简单,第二次调用方法没有执行,而是从缓存中直接获取了该方法的返回值。我们在Redis中也可以看到 缓存值:
在这里插入图片描述
当参数不止一个,那么缓存的值是怎么确定的呢?我们看下:

@Service
public class UserService {
    @Cacheable(cacheNames = "c1")
    public User getUserById(int id,String name){
        System.out.println("getUserById>>>" + id);
        User user = new User();
        user.setId(id);
        user.setName("macay");
        return  user;
    }
}
    @Test
    public void getUserById() {
        User user1 = userService.getUserById(1,"aa");
        System.out.println(user1);
        User user2 = userService.getUserById(1,"bb");
        System.out.println(user2);
    }
}

在这里插入图片描述
可以看到,当有多个参数时,且参数不一样时,它认为不是同一个方法,方法会都会执行。
那么,如果方法有多个参数,我只想使用id作为缓存的key,怎么实现呢?我们可以加一个属性key,如下:

@Service
public class UserService {
    @Cacheable(cacheNames = "c1",key = "#id")
    public User getUserById(int id,String name){
        System.out.println("getUserById>>>" + id);
        User user = new User();
        user.setId(id);
        user.setName("macay");
        return  user;
    }
}

同样的执行测试方法:

        @Test
        public void getUserById() {
            User user1 = userService.getUserById(1,"aa");
            System.out.println(user1);
            User user2 = userService.getUserById(1,"bb");
            System.out.println(user2);
        }
    }

在这里插入图片描述
可以看到,方法竟然一次都没有执行,直接返回了缓存中的值,这是为啥呢?因为前面执行方法的时候已经缓存了id 为key的值。

如果我们的key值特别复杂的话,我们也可以自定义一个key的生成器,比如我们这里使用方法的名称加参数作为key:

@Component
public class MyKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object o, Method method, Object... objects) {
        return method.getName()+":"+ Arrays.toString(objects);
    }
}
    @Cacheable(cacheNames = "c1",keyGenerator = "myKeyGenerator")
    public User getUserById(int id,String name){
        System.out.println("getUserById>>>" + id);
        User user = new User();
        user.setId(id);
        user.setName("macay");
        return  user;
    }

方法执行了两次,在Redis中可以看到:
在这里插入图片描述
如果我们某个方法是用来删除数据库的数据,那么如何来保证缓存中的数据呢?下面看下用于删除的注解@CacheEvict

@Service
public class UserService {
    @Cacheable(cacheNames = "c1")
    public User getUserById(int id){
        System.out.println("getUserById>>>" + id);
        User user = new User();
        user.setId(id);
        user.setName("macay");
        return  user;
    }
    @CacheEvict(cacheNames = "c1")
    public void deleteUserById(int id){
        System.out.println("deleteUserById>>>" + id);
    }
}
@SpringBootTest
public class UserServiceTest {
    @Autowired
    UserService userService;

        @Test
        public void getUserById() {
            User user1 = userService.getUserById(1);
            System.out.println(user1);
            userService.deleteUserById(1);
            User user2 = userService.getUserById(1);
            System.out.println(user2);
        }
    }

测试结果如下:
在这里插入图片描述
可以看到,get方法执行了两次,因为前面的delete方法已经删除了缓存中的数据。

最后我们来看下如果数据库中的数据更新了,如何保证缓存中的数据也同步更新呢?我们使用@CachePut注解来实现:

  @CachePut(cacheNames = "c1",key = "#user.id")
    public User updateUser(User user){
        return  user;
    }
@SpringBootTest
public class UserServiceTest {
    @Autowired
    UserService userService;

        @Test
        public void getUserById() {
            User user1 = userService.getUserById(1);
            System.out.println(user1);
            User user = new User();
            user.setId(1);
            user.setName("macay");
            user.setAddress("xian");
            userService.updateUser(user);
            User user2 = userService.getUserById(1);
            System.out.println(user2);
        }
    }

在这里插入图片描述
可以看到,单update方法执行后,缓存中的数据已更新,再次执行get方法得到的是最新的值,

当然我们也可以将cacheNames = “c1”,keyGenerator = "myKeyGenerator"等属性定义在一个类上面,作用于所有的方法。

5、总结

在Spring Boot中,使用Redis缓存,既可以使用RedisTemplate自己来实现,也可以使用使用这种方式,这种方式是Spring Cache提供的统一接口,实现既可以是Redis,也可以是Ehcache或者其他支持这种规范的缓存框架。从这个角度来说,Spring Cache和Redis、Ehcache的关系就像JDBC与各种数据库驱动的关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值