likes
comments
collection
share

SpringCache

作者站长头像
站长
· 阅读数 1

一、缓存

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

如何使用缓存?

SpringCache

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

如果数据库里数据变了,缓存没有同步进行变化,造成: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
methodNameroot对象当前被调用的方法名#root.methodname
methodroot对象当前被调用的方法#root.method.name
targetroot对象当前被调用的目标对象实例#root.target
targetClassroot对象当前被调用的目标对象的类#root.targetClass
argsroot对象当前被调用的方法的参数列表#root.args[0]
cachesroot对象当前被调用的方法使用的缓存列表#root.caches[0].name

注意:

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

6. 使用示例

6.1 起步依赖:

<!-- 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:

@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表达式

/**
 * 根据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);
}
测试效果

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

 @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));
    }
测试结果

SpringCache

6.4 清理缓存加@CacheEvict

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

  • 用法:

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

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

SpringCache

6.5 更新缓存加@CachePut

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

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

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

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

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

使用示例

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

/**
 * 新增用户方法
 * 注解@CachePut将会把方法返回值缓存起来:以cacheNames+key作为缓存的key,以方法返回值作为缓存的value
 */
@Override
@CachePut(cacheNames = "user", key = "#user.id")
public User addUser(User user) {
    userMapper.insert(user);
    return user;
}
测试效果
@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);
    }
}