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与各种数据库驱动的关系。