当前位置:   article > 正文

Springboot缓存

Springboot缓存

Springboot缓存

一.简介

Spring从3.1开始定义了org.springframework.cache.Cache和

org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-

107)注解简化我们开发;

Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;

Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCache ,ConcurrentMapCache

等;

每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如

果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用

直接从缓存中获取。

使用Spring缓存抽象时我们需要关注以下两点;

1、确定方法需要被缓存以及他们的缓存策略

2、从缓存中读取之前缓存存储的数据

适合使用缓存的情况:

1.数据访问频繁

2.数据变化非常小

二.启用缓存

1.Springboot启用缓存

1.1 pom中导入缓存启动器

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
  • 1
  • 2
  • 3
  • 4

1.2在主程序入口加上@EnableCaching注解启用缓存

@SpringBootApplication
@EnableTransactionManagement
@EnableCaching
public class SpringbootMabatisDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootMabatisDemoApplication.class, args);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这样之后就可以使用缓存了。

2.基于注解的缓存使用

@Cacheable: triggers cache population,缓存入口

@CacheEvict :triggers cache eviction,缓存清除

@CachePut :updates the cache without interfering with the method execution 在不影响方法执

行的情况下更新缓存

@Caching :regroups multiple cache operations to be applied on a method 将应用于方法的多

个缓存操作重新分组

@CacheConfig: shares some common cache-related settings at class-level在类级别共享一些常

见的缓存相关设置

2.1 @Cacheable(使用缓存示例)

//以传入的参数们作为 key(使用默认生成key的策略) 存入名为users的缓存中去, 
// 下次同样的参数调用 方法就不执行了,直接返回缓存中的数据
  @Cacheable(cacheNames = {"users"})
    public Tusers queryByUserName(String userName){
        System.out.println("登录 按用户名查询用户了");
        QueryWrapper<Tusers> qw = new QueryWrapper<>();
        qw.eq("usersn",userName);
          List<Tusers> users =userDao.selectList(qw);
          if (users.size()>0){
              return users.get(0);
          }else
              return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.1.1默认生成key的策略:

1.如果没有给出参数,则返回SimpleKey.EMPTY。

2.如果只给出一个参数,则返回该实例,也就是该参数传入的值作为key。

3.如果指定了多个参数,则返回一个包含所有参数的SimpleKey,也就是生成一个综合了所有参数值的

key。

要提供一个不同的默认密钥生成器,需要实现org.springframework.cache.interceptor.KeyGenerator

接口

2.1.2自定义key的策略声明

由于缓存是通用的,目标方法很可能具有各种不能简单映射到缓存结构顶部的签名。 当目标方法有多个

参数,其中只有一些适合缓存时(而其他参数仅由方法逻辑使用),这一点就变得很明显。 例如:

  @Cacheable(cacheNames = {"users"},key ="#userName")
    public Tusers queryByUserName(String userName){
        System.out.println("登录 按用户名查询用户了");
        QueryWrapper<Tusers> qw = new QueryWrapper<>();
        qw.eq("usersn",userName);
          List<Tusers> users =userDao.selectList(qw);
          if (users.size()>0){
              return users.get(0);
          }else
              return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

用户名作为缓存数据的key可能就够了(当然更可能不够,可能还需要密码)。

开发人员可以使用SpEL(Spring Expression Language)来挑选感兴趣的参数(或它们嵌套的属性)、执行操

作甚至调用任意方法,而无需编写任何代码或实现任何接口。 例如: #user.id

2.1.3 关于SpEL

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

名称位置描述示例
methodNameroot对象当前被调用的方法名#root.methodname
methodroot对象当前被调用的方法#root.method.name
targetroot对象当前被调用的目标对象实例#root.target
targetClassroot对象当前被调用的目标对象的类#root.targetClass
argsroot对象当前被调用的方法的参数列表#root.args[0]
cachesroot对象当前方法调用使用的缓存列表#root.caches[0].name
ArgumentName执行上下文当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数#artsian.id
result执行上下文方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false)#result

注意:

1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root

对象的属性。 如 :

@Cacheable(key = “targetClass + methodName +#p0”)

2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:

@Cacheable(value=“users”, key=“#id”)

@Cacheable(value=“users”, key=“#p0”)

p是paramter参数的意思 0是第0个的意思

SpEL提供了多种运算符

类型运算符
关系<,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术+,- ,* ,/,%,^
逻辑&&,!,and,or,not,between,instanceof
条件?: (ternary),?: (elvis)
正则表达式matches
其他类型?.,?[…],![…],1,$[…]

2.2 @CachePut annotation

对于需要在不影响方法执行的情况下更新缓存的情况,可以使用@CachePut注释。 也就是说,该方法将

始终被执行,其结果将被放置到缓存中(根据@CachePut选项)。 它支持与@Cacheable相同的选项,应

该用于缓存填充。

对数据的修改操作上,往往需要@CachePut 注解,因为数据更改了,那么缓存中的相同数据也要同步更

改。

注意,在同一个方法上使用@CachePut和@Cacheable注解通常是不被鼓励的,因为它们有不同的行

为。 后者会通过使用缓存跳过方法执行,而前者会强制执行以执行缓存更新。 这将导致意想不到的行

为,除了特定的特殊情况(例如注释具有相互排除的条件)外,应该避免这样的声明。 还要注意的是,这

种条件不应该依赖于结果对象(即#result变量),因为这些都是预先验证的,以确认排除。

2.3@CacheEvict annotation

缓存抽象不仅允许填充缓存存储,还允许清除缓存存储。 这个过程对于从缓存中删除过时或未使用的数

据非常有用。 与@Cacheable相反,@CacheEvict标注了执行缓存清除的方法,即充当触发器从缓存中

删除数据的方法。 就像它的兄弟一样,@CacheEvict需要指定一个(或多个)受该操作影响的缓存,允许

自定义缓存和键解析或指定条件,但另外,还有一个额外的参数allEntries,用于指示是否需要执行缓存

范围内的清除,而不仅仅是一个条目(基于键):

比如删除数据时,缓存中的也应该删除掉

2.4@Caching annotation

在某些情况下,需要指定多个相同类型的注释,例如@CacheEvict或@CachePut,因为不同缓存之间的

条件或键表达式不同。 @Caching允许在同一个方法上使用多个嵌套的@Cacheable, @CachePut和

@CacheEvict:

@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") }) public Book importBooks(String deposit, Date date)
  • 1

2.5@CacheConfig annotation

到目前为止,我们已经看到缓存操作提供了许多定制选项,可以在操作的基础上设置这些选项。 但是,

如果一些定制选项应用于类的所有操作,那么配置它们可能会很繁琐。 例如,为类的每个缓存操作指定

要使用的缓存名称可以用单个类级别定义代替。 这就是@CacheConfig发挥作用的地方

@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }
  • 1

三.缓存示例

3.1 当我们使用缓存后,再次进行相同操作,则不需要再次访问数据库了,直接从缓存中去获取即可,这便是缓存的方便之处。示例:

这里我用了一个叫"users"的缓存器,当我第一次执行登录时,会执行以下代码,并在控制台打印出"登录 按用户名查询用户了",还有相应的查询用户名的sql语句,但当第二次执行登录时,便不会执行下列方法,也不会打印sql语句,前提是同一用户.

  @Cacheable(cacheNames = {"users"},key ="#userName")
    public Tusers queryByUserName(String userName){
        System.out.println("登录 按用户名查询用户了");
        QueryWrapper<Tusers> qw = new QueryWrapper<>();
        qw.eq("usersn",userName);
          List<Tusers> users =userDao.selectList(qw);
          if (users.size()>0){
              return users.get(0);
          }else
              return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

第一次执行:

在这里插入图片描述

当我第二次执行登录时:
在这里插入图片描述

控制台信息为空。因为此次登录是直接从缓存中去取,不访问数据库。

另外我在分页上也用到了缓存器。分页缓存器名"topics"

 @Cacheable(cacheNames ="topics")
    public IPage<TvoteTopic> queryByCondition(TopicCondition con){
        System.out.println("分页");
        Page<TvoteTopic> page = new Page<>();
        page.setCurrent(con.getPageNo());
        page.setSize(con.getPageSize());
        QueryWrapper<TvoteTopic> qw = new QueryWrapper<>();
        if (con.getTopicName()!=null&&!con.getTopicName().isEmpty()){
            qw.like("topic",con.getTopicName());
        }
         return dao.selectPage(page,qw);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

当执行分页后,并会在控制台打印"分页",执行sql语句,在数据库中进行查找,再返回。

当我点击第一页时:

在这里插入图片描述

执行了sql语句.

当我再次点击第一页时,控制台没有打印:

在这里插入图片描述

当我点击第二页时:

在这里插入图片描述

控制台又打印了"分页"和sql语句。

第二次点击第二页:

在这里插入图片描述

控制台再次没有打印。

这说明了第二次点击是从缓存中获取,没有去再次访问数据库。

缓存的作用减少了数据的频繁访问,加快了访问速度!!!


  1. ↩︎

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/164590
推荐阅读
相关标签
  

闽ICP备14008679号