spring缓存
简介
spring从3.1开始就引入了对Cache的支持定义了org.springframework.cache.cache和 org.springframework.cache.CacheManager
接口来统一不同的缓存技术.并使用JCache(JSR-107)注解简化我们的开发.
其使用方法和原理都类似于spring对事物管理的支持,SpringCache是作用在方法上的,其核心思想是,当我们在调用一个缓存方法时,会把该方法的参数和返回值作为一个键值对存在缓存中.
Cache和CacheManager接口说明
- Cache接口包含缓存的各种操作集合,你操作缓存就是通过这个接口来操作的
- Cache接口下spring提供给了各种XXXCache的实现,比如RedisCache,EhCache,ConcurrentMapCache
- CacheManagaer定义了创建配置获取管理和控制多个唯一命名的Cache.这些Cache存在于CacheManager的上下文中
小结
每次需要缓存给你的方法时,Spring会检查指定参数的指定目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有调用方法并缓存结果后返回给用户.下次调用直接从缓存中获取
@Cacheable注解
@Cacheable这个注解,用它就是为了使用缓存的.所以我们可以先说一下缓存使用的步骤
开启基于注解的缓存,使用@EnableCaching标识在Springboot主启动类上
标注缓存注解即可
标注缓存注解
这里使用@Cacheable注解可以将运行结果缓存,以后查询相同的数据,直接从缓存中查询,不需要调用方法
参数
- cacheNames/value:用来指定缓存组件的名字
- key:缓存数据时使用的key,可以用它来指定.默认是使用方法参数的值(这个key可以使用spEl表达式来编写)
- keyGenerator:key的生成器.key和keyGenerator二选一使用
- cacheManager:可以用来指定缓存管理器.从哪个缓存管理器里面获取
- condition:可以用来指定符合条件的情况下才缓存
@CachePut
@cacheput的作用主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和@Cacheable不同的是,它每次都会触发真实的方法调用
参数 | 解释 | example |
---|---|---|
value | 缓存存在的名称,在spring配置中定义,必徐至少指定一个 | @CachePut(value=”my cache”) |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | @CachePut(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | @CachePut(value=”testcache”,condition=”#userName.length()>2”) |
@CachePut注释,可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新
@CachePut(value="accountCache",key="#account.getName()")// 更新accountCache 缓存
public Account updateAccount(Account account) {
return updateDB(account);
}
@CacheEvict
@CachEvict 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空
@CacheEvict 作用和配置方法
参数 | 解释 | example |
---|---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | @CacheEvict(value=”my cache”) |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | @CacheEvict(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | @CacheEvict(value=”testcache”,condition=”#userName.length()>2”) |
allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | @CachEvict(value=”testcache”,beforeInvocation=true) |
@CacheConfig
所有的@Cacheable()里面都有一个value=“xxx”的属性,这显然如果方法多了,写起来也是挺累的,如果可以一次性声明完 那就省事了,
所以,有了@CacheConfig这个配置,@CacheConfig is a class-level annotation that allows to share the cache names,如果你在你的方法写别的名字,那么依然以方法的名字为准。
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}
自定义缓存注解
比如之前的那个@Caching组合,会让方法上的注解显得整个代码比较乱,此时可以使用自定义注解把这些注解组合到一个注解中,如:
@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserSaveCache {
}
这样我们在方法上使用如下代码即可,整个代码显得比较干净。
@UserSaveCache
public User save(User user)
扩展
比如findByUsername时,不应该只放username–>user,应该连同id—>user和email—>user一起放入;这样下次如果按照id查找直接从缓存中就命中了
@Caching(
cacheable = {
@Cacheable(value = "user", key = "#username")
},
put = {
@CachePut(value = "user", key = "#result.id", condition = "#result != null"),
@CachePut(value = "user", key = "#result.email", condition = "#result != null")
}
)
public User findByUsername(final String username) {
System.out.println("cache miss, invoke find by username, username:" + username);
for (User user : users) {
if (user.getUsername().equals(username)) {
return user;
}
}
return null;
}
其实对于:id—>user;username—->user;email—>user;更好的方式可能是:id—>user;username—>id;email—>id;保证user只存一份;如:
@CachePut(value="cacheName", key="#user.username", cacheValue="#user.username")
public void save(User user)
@Cacheable(value="cacheName", key="#user.username", cacheValue="#caches[0].get(#caches[0].get(#username).get())")
public User findByUsername(String username)
mybatis缓存注解
@CacheNamespace的源码分析
@CacheNamespace注解主要用于mybatis二级缓存,等同于<cache>属性。默认情况下,MyBatis 3 没有开启二级缓存,要开启二级缓存,需要在SQL 映射文件(mapper.xml)中添加一行:
<mapper namespace="cn.mybatis.mydemo.mapper.StudentMapper">
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
</mapper>
的人提前还需要在配置文件中开启全局缓存
<setting name="cacheEnabled" value="true"/>
CacheNamespace是注解,其源码如下所示:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface CacheNamespace {
Class<? extends org.apache.ibatis.cache.Cache> implementation() default PerpetualCache.class;
Class<? extends org.apache.ibatis.cache.Cache> eviction() default LruCache.class;
long flushInterval() default 0;
int size() default 1024;
boolean readWrite() default true;
boolean blocking() default false;
Property[] properties() default {};
}
一级缓存
一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
二级缓存是Mapper(namespace)级别的缓存。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息,将查询到的用户信息存储到一级缓存中。
如果中间sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
二级缓存
二级缓存是namespace级别的默认不开启
第一次调用mapper下的sql去查询用户信息,查询到的信息会放到该mapper对应的耳机缓存区域内
第二次调用相同的namespace下的mapper映射文件中相同的sql去查询用户的信息.会去对应的耳机缓存内取结果
如果调用相同namespace下的mapper映射文件中增删改sql,并执行了commit操作.此时会清空该namespace下的二级缓存
刷新二级缓存
通过flushCache属性,可以控制select、insert、update、delete标签是否属性二级缓存
默认设置
*默认情况下如果是select语句,那么flushCache是false。
*如果是insert、update、delete语句,那么flushCache是true。
默认配置解读
*如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
*如果增删改语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。
flushCache设置如下:
<selectid="findUserById"parameterType="int"
resultType="com.kkb.mybatis.po.User"useCache="true"flushCache="true">
SELECT * FROM user WHERE id =#{id}
</select>