缓存技术在我们的开发中是很常用了,Spring从3.1版本开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术,并且支持JCache注解简化开发.
首先我们使用缓存前,要明白的是,java其实也有对缓存技术的规范的,所谓的JSR-107就是为Java Caching定义的一套规范,他一共定义了五个核心的接口,分别是:
不得不说,使用java原生的缓存规范来实现我们的需求是很麻烦的,所有spring才对JSR-107进行了抽象,简化为Cache
和CacheManager
来帮助我们开发,我们可以通过两张图来对比一下使用了spring缓存抽象和使用Java Caching的区别:
首先是原生的JCache:
Spring缓存抽象:
可以很明显的看到,我们使用Spring以后,仅仅只需要操作CacheManager就可以来方便进行开发.
要在SpringBoot中使用缓存,我们需要明白以下几个组件的意义:
现在我们来新建一个SpringBoot项目,创建过程不在赘述,项目结构如下:
在UserMapper接口中,定义了一系列对user表的CRUD操作:
package com.xiaojian.cache.mapper;
import com.xiaojian.cache.bean.User;
import org.apache.ibatis.annotations.*;
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
public User queryUserById(int id);
@Insert("INSERT INTO user (userName, passWord) VALUES (#{userName}, #{passWord})")
public int insertUser(User user);
@Delete("DELETE FROM user WHERE id = #{id}")
public int deleteUserById(int id);
@Update("UPDATE user SET userName=#{userName}, passWord=#{passWord} WHERE id=#{id}")
public int updateUser(User user);
}
实体类User
定义了和user表对应的实体bean.
OK,如果要在SpringBoot中使用缓存,首先我们要在启动类中使用注解@EnableCaching
来开启项目对缓存的支持,
现在,我们可以在需要缓存的数据方法上使用注解@Cacheable
如:
package com.xiaojian.cache.controller;
import com.xiaojian.cache.bean.User;
import com.xiaojian.cache.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/user/{id}")
@Cacheable(cacheNames = {"user"})
public User queryUser(@PathVariable("id") int id){
User user = userMapper.queryUserById(id);
if (null == user) {
return null;
}
return user;
}
}
上面的代码中,queryUser方法首次查到的User对象,会缓存到名称为user
的缓存中,这样以后在查询该数据的时候,就会优先从缓存中来获取,这一点可以通过控制台是否打印sql语句来验证.下面我们详细看一下各个注解的作用.
@Cacheable
运行流程:
方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;如果没有找到就创建一个cache组件,这里的获取是由CacheManager来完成的.
在找到的cache组件中,使用一个参数key来获取缓存的内容,默认是按照@Cacheable
注解所在的方法的参数,
key是按照一定的keyGenerator
生成的,默认使用SimpleKeyGenerator
生成的
SimpleKeyGenerator
生成key的规则:
如果没有参数, key = new SimoleKey()
如果有一个参数, key=参数的值
,也就是方法形参的值
如果有多个参数,key = new SimpleKey(params)
.
如果没有查到缓存数据,开始调用目标方法获取.
将目标方法得到的数据保存到缓存中.
Cacheable的其他几个属性
cacheNames/value : 指定缓存组件的名字,将方法的返回值存放在哪个缓存中,是数组的方式,可以指定多个缓存.
key : 缓存数据时使用的key,可以用这个属性来指定,默认使用方法参数的值.可以使用SqEL表达式来指定,比如#id方法参数id的值,#a0 #p0 #root.args[0]代表第一个参数,更多的取值参考下图:
keyGenerator : key的生成器,可以自己指定key的生成组件的id,也就是说,如果key不想使用key
属性来指定,就可以用这个参数来自定义生成规则,key
和keyGenerator
二选一即可.举例如下:
在这里,我们新建一个配置类如下:
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
//获取到@Cacheable注解所在的方法名后加上[和参数的值,再加上],将此作为缓存时的key
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
如上定义以后,就可以在@Cachable
的注解中引用了keyGenerator = "myKeyGenerator"
.
CacheManager : 指定缓存管理器,或者用CacheResolver来指定获取解析器.
condition : 在符合该条件的情况下才缓存数据.如:condition = "#id>0"
代表参数id必须大于0才缓存
unless : 否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断,如
unless = "#result == null"
或者unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
sync : 是否使用异步模式,默认为false,需要注意的是,如果开启异步,unless属性就不支持了.
@CachePut : 既调用方法,又更新缓存.
运行流程:
其他属性:
该注解的属性和@Cacheable
大同小异
需要注意的是,使用该注解更新缓存的时候,缓存的key一定要和@Cacheable注解指定的key一直,否则不能正常更新
@CacheEvict : 清除缓存
@Caching : 定义复杂的缓存规则
该注解下面又很的基本注解:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
可以看到,我们可以基于这个注解定义复杂逻辑的缓存实现,举例如下:
@Caching(
cacheable = {
@Cacheable(/*value="user",*/key = "#lastName")
},
put = {
@CachePut(/*value="user",*/key = "#result.id"),
@CachePut(/*value="user",*/key = "#result.email")
}
)
public User getUserByUserName(String userName){
return userMapper.getUserByUserName(userName);
}
@CacheConfig : 抽取缓存的公共配置
由于在以上的方法中,我们都使用了@Cacheable
将数据存在user
中,因此我们可以在类上使用@CacheConfig
注解来抽取共同的部分,:
@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/)
关于SpringBoot中缓存注解的使用,本文基本上已经涉及到,在使用用要灵活运用,在实际的开发中,我们一般都会使用Redis来实现缓存数据,在下一篇文章中,将会介绍SpringBoot使用Redis缓存.