SpringBoot专题学习Part25:Spring Cache缓存抽象的使用(@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig)

一、概念

缓存是每个系统都应该考虑的功能 用于加速系统的访问和提升系统的性能

例如一个电商网站的商品信息 如果每次都要查询数据库 耗时过大
此时 可以引入一个缓存中间件 将商品数据信息放入缓存中 就不用查询数据库了 直接查询缓存即可
缓存中若有 则直接使用 缓存中若没有 则查询数据库 并将数据信息放入缓存中
因为 应用程序和缓存的交互是非常快的 速度比查询数据库要快的多 尤其是数据库庞大的情况下

或者处理临时数据 比如验证码 三分钟有效 用完即删除
那么这些数据可以放入缓存中 系统从缓存中存取即可 若到时间则从缓存中删除
应用场景非常广泛

1、JSR-107

为了统一缓存的开发规范 并提升系统的扩展性
J2EE发布了JSR-107缓存规范

Java Caching定义了5个核心接口 分别为CachingProviderCacheManagerCacheEntryExpiry
其中:

  • CachingProvider(缓存提供者)定义 创建 配置 获取 管理和控制多个CacheManager(缓存管理器)
    一个应用可以在运行期访问多个CachingProvider

  • CacheManager(缓存管理器)定义 创建 配置 获取 管理和控制多个唯一命名的Cache
    这些Cache存在于CacheManager的上下文中
    一个CacheManager仅能被一个CachingProvider所拥有

  • Cache(缓存)是一个类似Map的数据结构并临时存储以Key为索引的值
    一个Cache仅能被一个CacheManager所拥有

  • Entry是一个存储在Cache中的key-value键值对

  • Expiry有效期
    每一个存储在Cache中的条目有一个定义的有效期 一旦超过这个时间 条目即为过期状态
    一旦过期 则条目将不可访问 更新和删除
    缓存有效期可通过ExpiryPolicy设置

在这里插入图片描述
JSR-107定义的都是一些接口 这样 类似于JDBC 直接面向接口编程
然鹅 但并不是市面上所有的缓存组件都提供了JSR-107的实现
若无类似的实现 则须自己写相应的实现
因此JSR-107的使用并不是很多

2、SpringCache缓存抽象

Spring从3.1之后引入了基于注解的缓存技术 本质并不是一个具体的缓存实现方案 而是一个对缓存使用的抽象
通过在既有代码中添加其定义的注解 即能够达到缓存的效果

定义了org.springframework.cache.Cacheorg.springframework.cache.CacheManager这两个接口来统一不同的缓存技术
并支持使用JCache(JSR-107)注解来简化开发
在这里插入图片描述

①、类

  • Cache缓存接口 用于定义缓存操作
    它的实现有RedisCache EhCacheCache ConcurrentMapCache等
    Cache接口的实现不同 则使用的缓存技术也不同
  • CacheManager缓存管理器 用于管理各种缓存组件(Cache)

②、注解

  • @Cacheable:对方法添加该注解 能够根据方法的请求参数对其结果进行缓存
    被该注解标注的结果将被缓存
    例:
@Cacheable
public User getUser(Integer id)
  • @CacheEvict:清空缓存
    例:
@CacheEvict
public void deleteUser(Integer id)
  • @CachePut:保证方法被调用 又希望结果被缓存
    用于缓存更新
    例:
@CachePut
publit User updateUser(User user)
  • @EnableCaching:标注在启动类上 开启基于注解的缓存

二、使用

在用向导创建项目的时候要引入Spring Cache模块
在这里插入图片描述
或者自己添加依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

然后 在主程序类上加上@EnableCaching注解 开启基于注解的缓存

注:必须开启 否则无法使用缓存
@MapperScan("net.zjitc.springboot.mapper")
@SpringBootApplication
@EnableCaching
public class SpringbootCacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootCacheApplication.class, args);
    }
}

之后就是在方法上添加相应的缓存注解了:
在案例中 我用的是MyBatis 当然 其它持久层框架也可以

1、@Cacheable注解:

使用@Cacheable注解 将方法的运行结果进行缓存
之后再需要获取相同的数据时 直接可从缓存中获取

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    @Cacheable(cacheNames = "emp",key = "#id")
    public Employee findEmployee(Integer id)
    {
        return employeeMapper.findEmployeeById(id);
    }
}

@Cacheable注解中的属性:

  • cacheNamevalue指定缓存的名称
    CacheManager缓存管理器是可以管理多个缓存组件的
    对缓存的真正操作是在缓冲组件中 每一个缓存组件有其唯一的名字
    可用数组的形式来指定多个名称 逗号分隔
  • key指定缓存数据时使用的key
    数据是以key-value键值对的形式进行存储的
    默认使用方法参数的值 例如 输入1 返回User1 那么key为1 value为User1
    可用spEL表达式
    #a0=#p0=#root.args[0]
    还能进行拼接 例:"#root.methodName+’[’+#id+’]’"
  • keyGenerator指定key生成器 默认是用参数值作为缓存key 可自己用生成器生成
    key和keyGenerator两者只能二选一
  • cacheManager指定缓存管理器
  • cacheResolver指定缓存解析器
    cacheResolver得到的是缓存的集合 cacheManager得到的是单个缓存
    cacheResolver和cacheManager二选一
  • condition指定符合条件的情况下才进行缓存
  • unless指定符合条件的情况下不进行缓存
    和condition相反
    例:unless=#result==null 结果为空不进行缓存
  • sync是否使用异步模式进行缓存

spEL表达式:
在这里插入图片描述
执行流程
@Cacheable注解标注的方法执行之前先检查缓存中是否有该数据 默认按照参数的值作为key去查询缓存
若没有值 则调用查询数据库的方法 并将结果放入缓存中
若有值 则拿到数据 直接返回 不会调用查询数据库的方法

核心
使用CacheManager根据名字得到Cache组件
CacheManager默认用的是ConcurrentMapCacheManager
Cache默认用的是ConcurrentMapCache

key使用keyGenerator来生成
默认用的是SimpleKeyGenerator


keyGenerator属性 指定key生成器

自己写一个配置类 对其进行配置:

@Configuration
public class MyCacheConfig {

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator()
    {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName()+"["+ Arrays.asList(params).toString()+"]";
            }
        };
    }
}

然后在keyGenerator属性里填入生成器的名称即可

@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")
public Employee findEmployee(Integer id)
{
    System.out.println("查询"+id+"号员工");
    return employeeMapper.findEmployeeById(id);
}

condition属性 指定缓存条件
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator",condition = "#a0==2")
public Employee findEmployee(Integer id)
{
    System.out.println("查询"+id+"号员工");
    return employeeMapper.findEmployeeById(id);
}

condition = "#a0==2"的意思是:当第1个参数为2时 才进行缓存
还可添加更多条件 然后进行连接 例如and eq等 实现多重判断


unless属性 指定不缓存条件
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator",unless = "#a0==2")
public Employee findEmployee(Integer id)
{
    System.out.println("查询"+id+"号员工");
    return employeeMapper.findEmployeeById(id);
}

unless = "#a0==2"的意思是:当第1个参数不为2时 才进行缓存
condition和unless可同时存在


2、@CachePut注解:

既调用方法 又更新数据
通常用于修改数据的更新方法

@CachePut(cacheNames = "emp")
public Employee updateEmployee(Employee employee)
{
    employeeMapper.updateEmployee(employee);
    return employee;
}

@CachePut的执行时机和@Cacheable不同
@Cacheable在方法调用之前进行判断
@CachePut先调用方法 然后将方法的结果放入缓存中
无论如何 方法都会被执行到

有一个需要注意的点:
默认key的值是参数 因此@CachePut的key默认是传入的对象
而@Cacheable是key默认是查询时传入的参数
所以它们两个数据是都会被存放在缓存中 更新后的数据不会覆盖缓存中的数据
因此 若要覆盖 必须指定统一的key 相当于是门牌号 得相同 才可互相识别

@CachePut(cacheNames = "emp",key = "#employee.id")
public Employee updateEmployee(Employee employee)
{
    System.out.println("更新了 最新数据:"+employee);
    employeeMapper.updateEmployee(employee);
    return employee;
}

@CachePut(cacheNames = "emp",key = "#result.id")
public Employee updateEmployee(Employee employee)
{
    System.out.println("更新了 最新数据:"+employee);
    employeeMapper.updateEmployee(employee);
    return employee;
}

这两种写法都可以 #employee.id是从入参中取属性值 而**#result.id**是从结果中取属性值
对于更新操作来说 入参属性值和更新后结果属性值是一样的 且@CachePut标注的方法必定会被执行 因此一定是有result结果的
而@Cacheable就不能从结果中取值了 因为@Cacheable标注的方法不一定会被执行 因而 不一定会有结果


3、@CacheEvict注解

该注解用于清除缓存
通过key属性来指定要清除的Cache
若不设置key 则默认使用传入的参数值

@CacheEvict(cacheNames = "emp",key = "#id")
public void deleteEmployee(Integer id)
{
    System.out.println("删除了"+id);
    employeeMapper.deleteEmployee(id);
}
allEntries属性 是否删除指定缓存中的所有数据
@CacheEvict(cacheNames = "emp",allEntries = true)
public Integer deleteEmployee(Integer id)
{
    System.out.println("删除了"+id);
    employeeMapper.deleteEmployee(id);
    return id;
}

beforeInvocation属性 缓存中数据的清除是否在方法执行之前

默认为false 即 在方法执行之后清除
区别就是 在方法执行之后清除的话 若是在方法执行过程中出错 则缓存不会被清除
而在方法执行之前清除 无论方法执行是否出错 缓存都会被清除

@CacheEvict(cacheNames = "emp",allEntries = true,beforeInvocation = true)
public Integer deleteEmployee(Integer id)
{
    System.out.println("删除了"+id);
    employeeMapper.deleteEmployee(id);
    return id;
}

4、@Caching注解

@Caching注解是@Cacheable注解和@CachePut注解和@CacheEvict注解的综合版
是个三合一注解 用于定义复杂的缓存规则
内部是这样的:

public @interface Caching {

	Cacheable[] cacheable() default {};

	CachePut[] put() default {};

	CacheEvict[] evict() default {};

}

当缓存规则比较复杂的时候可使用该注解 以同时指定多个规则

@Caching(cacheable = {@Cacheable(cacheNames="emp",key="#lastName")},
        put = {@CachePut(cacheNames="emp",key="#result.id"),
                @CachePut(cacheNames="emp",key="#result.email")})
public Employee getEmployeeByLastName(String lastName)
{
    return employeeMapper.getEmployeeByLastName(lastName);
}

5、@CacheConfig注解

该注解加在类上 用于指定该类中缓存配置的公共属性
内部是这样的:

public @interface CacheConfig {

	String[] cacheNames() default {};

	String keyGenerator() default "";

	String cacheManager() default "";

	String cacheResolver() default "";

}

指定之后 该类里面所有缓存注解的相应属性都可以不用配置了:

@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {

    @Caching(cacheable = {@Cacheable(key="#lastName")},
                put = {@CachePut(key="#result.id"),
                        @CachePut(key="#result.email")})
        public Employee getEmployeeByLastName(String lastName)
        {
            return employeeMapper.getEmployeeByLastName(lastName);
        }
}

发布了174 篇原创文章 · 获赞 5 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/Piconjo/article/details/105160437
今日推荐