SpringCache

一、缓存

Redis作为"缓存"。缓存:数据的高速临时存取的技术

如何使用缓存?

如果数据库里数据变了,缓存怎么办?

如果数据库里数据变了,缓存没有同步进行变化,造成:MySQL和Redis数据不一致

解决方案:

  • 方案1:对数据库里的数据 做什么样的修改,也对Redis里的缓存做同样的修改

  • 方案2:数据库里的数据 无论有任何变化,直接清除Redis缓存。客户端查询时没有缓存会重新从数据库里查询,再放到缓存里。

建议采用方案2,方案1太繁琐。

从上述分析使用缓存:

  • 当查询数据时

    优先从Redis里查询缓存,如果查询到缓存的数据,就直接返回给客户端

    如果查不到缓存数据:从数据库里查询得到数据,把数据缓存到Redis里,然后再给客户端返回结果

  • 当数据有变化时:增、删、改时

    要防止出现数据不一致问题:MySQL里的数据被修改了,缓存Redis里还是旧数据

    解决的方案:可以在增、删、改成功之后,清除掉缓存

每次操作查询都需要在原有业务代码前后各加一部分代码,操作增、删、改 都需要清除缓存,很多代码重复率高,所以可以想到,在不改变原有业务代码的情况下,对原有业务增强,我们可以使用AOP对其方法经行增强。然后发现使用AOP虽然可以完成功能增强,但是代码写起来会很繁琐,比如在考虑切入点时,我们不可能做到每个方法命名都提前固定好,所以要是使用注解方式,定义注解还需要设定value值来区分要增强的方法等等。

所以Sping就提供了SpringCache 下面就来介绍一下SpringCache以及使用方式和注意细节。

二、SpringCache

1. 介绍

在企业开发中,缓存对于提升程序性能有非常大的作用,所以已经广泛应用于企业项目开发中。但是缓存技术是多种多样的,例如Redis、Caffeine、MemCache、EhCache等等。而不同的缓存技术,其操作方法并不统一,并且还需要开发人员编写代码实现缓存操作。

从Spring3.1版本开始,Spring就利用AOP思想,对不同的缓存技术做了再封装,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。让开发人员只专注于业务,不需要再关心具体的缓存技术。

2. 使用步骤

  1. 添加起步依赖:spring cache的依赖坐标,缓存技术比如redis的坐标

  2. 在引导类上添加注解 @EnableCaching,开启缓存功能

  3. 在方法上添加注解使用缓存:

    • 方法查询数据使用缓存:@Cacheable(cacheName="",key="")

      缓存的键是:cacheName::key

      缓存的值是:方法的返回值

    • 方法执行后想要清除缓存:@CacheEvict(cacheName="",key="")

      把键为cacheName::key对应的数据清除掉

    • 方法执行后想要更新缓存:@CachePut(cacheName="",key="")

      更新哪个键的:cacheName::key

      把值更新成:方法的返回值,会把Redis里原来的值给覆盖掉

3. 使用要求

  • SpringCache结合Redis时,默认会使用JDK序列化方式,将数据序列化成字节数组,再缓存起来。
  • 所以实现类要实现Serializable接口

4. 常用注解

注解 说明
@EnableCaching 开启缓存注解功能,通常加在启动类上
@Cacheable 在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中
@CachePut 将方法的返回值更新到缓存中
@CacheEvict 将一条或多条数据从缓存中删除。哪个方法执行后想要清除缓存,就在方法上加这个注解

5. SpEL表达式

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

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

注意:

  • 使用方法参数时,可以直接写成#参数名,也可以写成:#p参数索引,例如#p0表示索引0的参数

6. 使用示例

6.1 起步依赖:

xml 复制代码
<!-- SpringCache起步依赖坐标 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Redis起步依赖坐标 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

如果只添加一个SpringCache依赖的话,springCache默认将会使用ConcurrentHashMap作为缓存容器。但是Spring Cache 其实提供了一层抽象,底层可以切换不同的缓存实现,例如:

  • EHCache,如果添加了EHCache的依赖坐标,SpringCache将会使用EhCache作为缓存容器
  • Caffeine,如果添加了caffeine的依赖坐标,SpringCache将会使用Caffeine作为缓存容器
  • Redis(常用),如果添加了Redis依赖坐标,SpringCache将会使用Redis作为缓存容器
  • ......

6.2 引导类上加注解@EnableCaching:

java 复制代码
@EnableCaching //开启声明式缓存功能
@SpringBootApplication
public class CacheDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(CacheDemoApplication.class, args);
    }
}

6.3 使用缓存加@Cacheable

@Cacheable 说明:
  • 作用:在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;

    若没有数据,调用方法并将方法返回值放到缓存中

  • 用法:@Cacheable(cacheNames="", key="")

    缓存的key:以cacheNames::key的值作为key,查找对应的值

  • 注意:注解里的key支持SpringEL表达式

java 复制代码
/**
 * 根据id查询用户
 * Spring会优先从缓存中查找:以cacheNames::key作为key,查找对应的值
 *  如果找到了,就会直接返回结果;这个方法是不会执行的
 *  如果找不到,才会执行这个方法,并把方法的返回值缓存起来
 */
@Override
@Cacheable(cacheNames = "user", key = "#id")
public User queryById(Long id) {
    System.out.println(">>>>UserServiceImpl.queryById方法执行了");
    return userMapper.selectById(id);
}
测试效果

然后在测试类里增加测试方法,并执行:

java 复制代码
 @Test
    public void testCacheable(){
        System.out.println("--------第一次查询用户1,没有缓存,会执行SQL语句从数据库里查询,然后把记过存到缓存里");
        System.out.println(userService.queryById(1L));

        System.out.println("--------第二次查询用户1,缓存里有数据,直接取缓存,不会执行SQL语句了 ");
        System.out.println(userService.queryById(1L));

        System.out.println("--------第三次查询用户1,缓存里有数据,直接取缓存,不会执行SQL语句了");
        System.out.println(userService.queryById(1L));
    }
测试结果

6.4 清理缓存加@CacheEvict

@CacheEvict 说明
  • 作用:清理指定缓存

  • 用法:

    • 用法1:CacheEvict(cacheNames="", key=""),清除cacheNames::key对应的缓存
    • 用法2:CacheEvict(cacheNames="", allEntries=true),清理所有以cacheNames::开头的key对应的缓存
  • 注意:注解里的key支持SpringEL表达式

使用示例
java 复制代码
@Override
@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {
    System.out.println(">>>>UserServiceImpl.deleteUser方法执行了");
    userMapper.deleteById(id);
}
测试效果
java 复制代码
@Test
public void testCacheEvict(){
    //从数据库里删除掉用户1,并且也会从缓存中清除掉 user::1 对应的缓存
    userService.deleteUser(1L);
}
测试结果

6.5 更新缓存加@CachePut

@CachePut 说明
  • 作用:将方法返回值,放入缓存(更新缓存)

  • 用法:@CachePut(cacheNames="", key="")

    • 缓存的key:Spring将使用 cacheNames的值::key的值作为缓存的key

    • 缓存的值:Spring将方法的返回值作为缓存的value

  • 注意:注解里的key支持SpringEL表达式

使用示例

如果在做新增操作,或者修改操作时,可以更新缓存:当新增或修改操作后,希望把最新的数据缓存起来,方便后续使用。可以在新增或修改方法上加注解@CachePut

java 复制代码
/**
 * 新增用户方法
 * 注解@CachePut将会把方法返回值缓存起来:以cacheNames+key作为缓存的key,以方法返回值作为缓存的value
 */
@Override
@CachePut(cacheNames = "user", key = "#user.id")
public User addUser(User user) {
    userMapper.insert(user);
    return user;
}
测试效果
java 复制代码
@SpringBootTest
public class CacheTest {
    @Autowired
    private UserService userService;

    @Test
    public void testCachePut(){
        User user = new User();
        user.setName("pony");
        user.setAge(60);
        //新增完成后,数据库里会多一条数据,使用AnotherRedisDesktopManager连接Redis,会发现也有此用户的缓存
        userService.addUser(user);
    }
}
相关推荐
爱写代码的刚子几秒前
C++知识总结
java·开发语言·c++
冷琴19968 分钟前
基于java+springboot的酒店预定网站、酒店客房管理系统
java·开发语言·spring boot
九圣残炎31 分钟前
【springboot】简易模块化开发项目整合Redis
spring boot·redis·后端
daiyang123...34 分钟前
IT 行业的就业情况
java
Nightselfhurt1 小时前
Spring cloud 中gateway原理
spring·spring cloud·gateway
爬山算法1 小时前
Maven(6)如何使用Maven进行项目构建?
java·maven
.生产的驴1 小时前
Electron Vue框架环境搭建 Vue3环境搭建
java·前端·vue.js·spring boot·后端·electron·ecmascript
爱学的小涛1 小时前
【NIO基础】基于 NIO 中的组件实现对文件的操作(文件编程),FileChannel 详解
java·开发语言·笔记·后端·nio
吹老师个人app编程教学1 小时前
详解Java中的BIO、NIO、AIO
java·开发语言·nio
爱学的小涛1 小时前
【NIO基础】NIO(非阻塞 I/O)和 IO(传统 I/O)的区别,以及 NIO 的三大组件详解
java·开发语言·笔记·后端·nio