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>
Last modification:July 20, 2022
如果觉得我的文章对你有用,请随意赞赏