Spring Cache组件

《Spring Cache组件》

提示: 本材料只做个人学习参考,不作为系统的学习流程,请注意识别!!!



1. Spring Cache组件概述

已经清楚了缓存是进行数据操作性能提升的重要手段,所有的数据最终都要分散在磁盘中进行数据的存储,所以在传统的开发中,很多的ORMapping组件(Hibernate、JPA、Mybatis),会进行缓存操作的实现,以提升数据库的访问性能。但是这样的实现方式会存在一个严重的问题,它只能够在数据层上实现,数据层实现缓存虽然很好,但是会存在有一个业务上的偏差,按照正规的设计思想来说,一个业务会牵扯到很多个数据的操作,而且一个业务的展现也可能存在有多个不同的数据层的缓存处理,相当于此时需要在不同的数据层上分别配置缓存,这样的设计就显得非常麻烦,而为了解决业务层上的缓存处理,所以提供了SpringCache缓存支持。

项目应用中通过缓存组件可以实现应用的处理性能,但是在现实的开发环境中,会存在有不同的缓存组件,例如:常见的单机版缓存组件包括Caffeine、EHCache,常见的分布式缓存组件包括Memcached、Redis。

在这里插入图片描述

由于现在的业务层已经提供了缓存的处理支持,所以数据层上就不再需要进行任何的缓存控制了(适合于整合各类的ORM框架),所以为了更好理解SpringCache的处理操作,那么下面首先采用标准的结构进行一个基础的应用设计。为了更好的理解SpringCache的处理操作,那么下面首先采用标准的结构进行一个基础的应用设计。

2. ConcurrentHashMap缓存管理

在进行缓存实现的时候,Spring会考虑三种的缓存实现方式:JDK内置的缓存实现(ConcurrentHashMap)、第三方的缓存组件(EHCache、Caffeine)、分布式的缓存实现(Memcached、Redis)。

ConcurrentHashMap是在J.U.C之中提供的最为重要的技术实现,它可以保证更新安全的前提下,提供良好的数据获取性能,在没有引入任何额外配置的时候,Spring缓存主要使用ConcurrentHashMap操作。

SpringCache之中为了便于缓存结构的管理,在org.springframework.cache包中提供了两个核心的标准接口,分别是:Cache接口、CacheManager管理接口。

在这里插入图片描述

Cache接口规定了缓存数据的保存、增加、失效以及清空处理的操作功能,而想获取到Cache接口实例,那么就需要通过CacheManeger接口方法完成(工厂类型),所有Cache对象都在CacheManager之中保存。

在这里插入图片描述
在进行缓存实现过程中,Spring是基于Cache接口提供的方法进行缓存操作的,所以不同的缓存组件如果要接入到Spring之中,则需要提供Cache接口的具体实现子类,考虑到缓存的管理问题,在Spring中又提供了CacheManager接口,所有可以在应用中使用的Cache类型全部在该接口之中进行配置。

ConcurrentMapCache缓存

本次将采用SpringCache的默认实现,使用org.springframework.cache.Cache内置的缓存实现类进行处理,当前类是ConcurrentMapCache,通过内置的ConcurrentHashMap属性实现缓存数据的存储。

public class ConcurrentMapCache extends AbstractValueAdaptingCache {
    private final String name;//SpringCache内置的需要,要提供一个名称
    private final ConcurrentMap<Object, Object> store;//实现缓存数据存储的集合

}
  1. 创建一个自定义的CacheConfig配置类,定义CacheManager接口实例
package com.personal.caffeine.springcache.config;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashSet;
import java.util.Set;

@Configuration//配置类
@EnableCaching//开启缓存
public class CacheConfig {//缓存配置类

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();//获取缓存管理接口实例
        Set<Cache> caches = new HashSet<>();//保存全部缓存的集合
        caches.add(new ConcurrentMapCache("emp"));//创建一个雇员缓存
        caches.add(new ConcurrentMapCache("dept"));//创建一个部门缓存
        caches.add(new ConcurrentMapCache("sal"));//创建一个工资缓存
        cacheManager.setCaches(caches);//将缓存放入到缓存管理器中
        return cacheManager;
    }
}

编写Service层代码

package com.personal.caffeine.springcache.service;

import com.personal.caffeine.springcache.dao.IEmpDao;
import com.personal.caffeine.springcache.po.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class IEmpService {

    @Autowired
    private IEmpDao empDao;

    //编辑雇员
    public Emp edit(Emp emp) {
        return empDao.edit(emp);
    }

    //删除雇员信息
    public boolean delete(String eid) {
        return empDao.delete(eid);
    }

    //根据id查询雇员信息
    @Cacheable(cacheNames = "emp")
    public Emp get(String eid) {
        return empDao.get(eid);
    }

    //根据名称查询雇员信息
    @Cacheable(cacheNames = "emp")
    public Emp getEname(String ename) {
        return empDao.getEname(ename);
    }


}

编写Dao层代码(模拟查询数据库)

package com.personal.caffeine.springcache.service;

import com.personal.caffeine.springcache.dao.IEmpDao;
import com.personal.caffeine.springcache.po.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class IEmpService {

    @Autowired
    private IEmpDao empDao;

    //编辑雇员
    public Emp edit(Emp emp) {
        return empDao.edit(emp);
    }

    //删除雇员信息
    public boolean delete(String eid) {
        return empDao.delete(eid);
    }

    //根据id查询雇员信息
    @Cacheable(cacheNames = "emp")
    public Emp get(String eid) {
        return empDao.get(eid);
    }

    //根据名称查询雇员信息
    @Cacheable(cacheNames = "emp")
    public Emp getEname(String ename) {
        return empDao.getEname(ename);
    }


}

编写实体类

package com.personal.caffeine.springcache.po;

import lombok.Builder;
import lombok.Data;
import lombok.ToString;

@Data
@ToString
@Builder
public class Emp {
    private String eid;
    private String ename;
    private String job;
    private Double salary;
}

编写测试类

package com.personal.caffeine;

import com.personal.caffeine.springcache.po.Emp;
import com.personal.caffeine.springcache.service.IEmpService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@Slf4j
@SpringBootTest
class CaffeineApplicationTests {

    @Autowired
    private IEmpService empService;

    @Test
    void testGetCache() {
        Emp emp1 = empService.get("1");
        log.info("[第一次查询],emp1:{}", emp1);

        Emp emp2 = empService.get("1");
        log.info("[第二次查询],emp2:{}", emp2);
    }

    @Test
    void testGetEname() {
        Emp emp1 = empService.getEname("张三");
        log.info("[第一次查询],emp1:{}", emp1);

        Emp emp2 = empService.getEname("张三");
        log.info("[第二次查询],emp2:{}", emp2);
    }

}

测试结果如下:

[持久层],ename:张三, emp:Emp(eid=1, ename=李四, job=总监, salary=20000.0)
[第一次查询],emp1:Emp(eid=1, ename=李四, job=总监, salary=20000.0)
[第二次查询],emp2:Emp(eid=1, ename=李四, job=总监, salary=20000.0)

一个非常简单的注解,可以直接在业务层上使用,就非常轻松的实现了缓存操作的处理,整体的实现效果是非常简单的,同时也避免影响其他数据层的缓存操作。

3. @Cacheable详解

在业务层之中如果想要使用缓存,则在方法上添加@Cacheable注解即可启用,但是实际上@Cacheable注解的内部也是提供有很多属性的。

Cacheable注解属性

在这里插入图片描述
在使用@Cacheable注解的时候,里面会有两个核心的配置属性,一个缓存条件,一个缓存的排除,如果要想进行这两项的配置,那么还需要使用特定的SPEL语法标记。

缓存的逻辑:缓存空间开始没有任何的数据项,而后通过数据层进行数据加载,随后直接拽入到缓存空间之中,以实现缓存数据的存储,但是现在可能某些数据是不需要进行缓存的,所以必须设置一些缓存配置的条件。

缓存配置中的SPEL上下文数据

在这里插入图片描述

  1. 在进行缓存的时候将雇员编号作为缓存的KEY
   //根据id查询雇员信息
    @Cacheable(cacheNames = "emp", key = "#eid")
    public Emp get(String eid) {
        return empDao.get(eid);
    }
[持久层],eid:1, emp:Emp(eid=1, ename=张三, job=经理, salary=10000.0)
[第一次查询],emp1:Emp(eid=1, ename=张三, job=经理, salary=10000.0)
[第二次查询],emp2:Emp(eid=1, ename=张三, job=经理, salary=10000.0)
  1. 默认情况下只要进行了数据的查询,那么肯定所有的数据都要求进行缓存处理了,可是现在要求对缓存的数据追加一些标记,判断查询的参数里面包含有指定的字符串才允许缓存。
 //根据id查询雇员信息
    @Cacheable(cacheNames = "emp", key = "#eid", condition = "#eid.contains('1')")
    public Emp get(String eid) {
        return empDao.get(eid);
    }

eid传入为2时,不符合#eid.contains(‘1’),顾不进行缓存

[持久层],eid:2, emp:Emp(eid=1, ename=张三, job=经理, salary=10000.0)
[第一次查询],emp1:Emp(eid=1, ename=张三, job=经理, salary=10000.0)
[持久层],eid:2, emp:Emp(eid=1, ename=张三, job=经理, salary=10000.0)
[第二次查询],emp2:Emp(eid=1, ename=张三, job=经理, salary=10000.0)
  1. 将雇员查询结果中工资低于5000的用户不进行缓存处理,需要追加排除配置。
    //根据id查询雇员信息
    @Cacheable(cacheNames = "emp", key = "#eid", unless = "#result.salary<5000")
    public Emp get(String eid) {
        return empDao.get(eid);
    }

当查询结果的salary为10000时,进行了缓存

[持久层],eid:1, emp:Emp(eid=1, ename=张三, job=经理, salary=10000.0)
[第一次查询],emp1:Emp(eid=1, ename=张三, job=经理, salary=10000.0)
[第二次查询],emp2:Emp(eid=1, ename=张三, job=经理, salary=10000.0)

当查询结果的salary为1000时,未触发缓存操作

[持久层],eid:1, emp:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[第一次查询],emp1:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[持久层],eid:1, emp:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[第二次查询],emp2:Emp(eid=1, ename=张三, job=经理, salary=1000.0)

业务层之中的缓存是针对所有数据的,但是如果某些数据在设计的时候没有达到缓存的要求,就将其进行缓存了,这样会造成缓存数据的污染,从而产生严重的缓存处理性能。

  1. 如果说现在是在多线程的处理环境下进行了缓存的查询,有可能会造成缓存穿透问题。现在假设有三个线程查询数据,但是三个线程判断的时候缓存都不存在,所以必然会发生三个线程同时查询数据库操作的可能,此时可以考虑同步缓存操作。
    //根据id查询雇员信息
    @Cacheable(cacheNames = "emp", key = "#eid", unless = "#result.salary<5000", sync = true)
    public Emp get(String eid) {
        return empDao.get(eid);
    }

当启用了同步处理之后,会由一个线程向数据库发出查询指令,而后自动进行缓存处理,而其他等待的线程,等到缓存中有数据之后才会继续进行缓存数据的获取。

4. Caffeine缓存管理

使用ConcurrentHashMap实现的缓存处理性能一定不如Caffeine好,因为Caffeine内部在数据实现的结构上会更加的优秀,那么既然现在要使用SpringCache,最佳的做法就是采用Caffeine作为单机的缓存操作。

如果想要使用不同的缓存组件,其最为核心的话题就是CacheManager以及Cache接口的实现,通过当前的配置项,可以发现,此时的程序内部已经提供了CaffeineCacheManager缓存管理类。

在这里插入图片描述

  1. 写法一:修改配置类,采用Caffeine缓存:

添加pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
@Configuration//配置类
@EnableCaching//开启缓存
public class CacheConfig {//缓存配置类

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                .maximumSize(100).expireAfterAccess(3L, TimeUnit.SECONDS);

        cacheManager.setCaffeine(caffeine);
        cacheManager.setCacheNames(Arrays.asList("emp"));//设置缓存名称
        return cacheManager;
    }
}

缓存生效

[持久层],eid:1, emp:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[第一次查询],emp1:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[第二次查询],emp2:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
  1. 写法二:修改配置类,采用Caffeine缓存:
@Configuration//配置类
@EnableCaching//开启缓存
public class CacheConfig {//缓存配置类

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();//获取缓存管理接口实例
        Set<Cache> caches = new HashSet<>();//保存全部缓存的集合
        caches.add(new CaffeineCache("emp", Caffeine.newBuilder().build()));
        cacheManager.setCaches(caches);//将缓存放入到缓存管理器中
        return cacheManager;
    }
}

下面给出一个实际使用的案例,可供参考

package com.chuanglan.geteway.server.config.caffeine;

import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.github.benmanes.caffeine.cache.Caffeine;

@Configuration
@EnableCaching
public class CaffeineConfig {

	private static final int DEFAULT_MAXSIZE = 500000;
    private static final int DEFAULT_TTL = 3600 * 24;

    /**
     * 定义cache名称、超时时长秒、最大个数
     * 每个cache缺省一个月过期,最大个数500000
     */
    public enum Caches {
    	
    	gateway_v2_settle_account(60 * 5,200),
    	gateway_v2_biz_ext_type(6*60*60,50000),
    	gateway_v2_biz_type(6*60*60,50000),
    	gateway_v2_biz_type_cost_fee(6*60*60,50000),
        gateway_v2_biz_type_activate(6*60*60,50000),
    	gateway_v2_product(6*60*60,5000),
    	gateway_v2_project(6*60*60,500000),
    	gateway_v2_voiceAccounts(5*60,200),
    	gateway_v2_account(6*60*60,500000),
    	gateway_v2_charge_model(6*60*60,500000),
        gateway_v2_cl_living_parent_app_info(24*60*60,500000),
        gateway_v2_cl_living_android_app_info(24*60*60,500000),
        gateway_v2_cl_living_ios_app_info(24*60*60,500000),
        gateway_v2_cl_living_app_info(24*60*60,500000),
        gateway_v2_acc_no_default_project(24*60*60,500000),
        ;

        Caches() {
        }

        Caches(int ttl) {
            this.ttl = ttl;
        }

        Caches(int ttl, int maxSize) {
            this.ttl = ttl;
            this.maxSize = maxSize;
        }

        private int maxSize = DEFAULT_MAXSIZE;    //最大數量
        private int ttl = DEFAULT_TTL;        //过期时间(秒)

        public int getMaxSize() {
            return maxSize;
        }

        public void setMaxSize(int maxSize) {
            this.maxSize = maxSize;
        }

        public int getTtl() {
            return ttl;
        }

        public void setTtl(int ttl) {
            this.ttl = ttl;
        }
    }
    
	/**
     * 个性化配置缓存
     */
    @Bean("caffeineCacheManager")
    public CacheManager cacheManager() {
        SimpleCacheManager manager = new SimpleCacheManager();
        ArrayList<CaffeineCache> caches = new ArrayList<CaffeineCache>();
        for (Caches c : Caches.values()) {
            caches.add(new CaffeineCache(c.name(), Caffeine.newBuilder()
            		.recordStats()
            		.expireAfterWrite(c.getTtl(), TimeUnit.SECONDS)
            		.maximumSize(c.getMaxSize()).build()));
        }
        manager.setCaches(caches);
        return manager;
    }
}

    @Cacheable(cacheManager = "caffeineCacheManager", cacheNames = CaffeineConstant.CACHE_PRODUCT, key = "'pd' + #projectCode", unless = "#result == null || #result.size() < 1")
    public List<Product> getProd(String projectCode) {
        return productMapper.selectProd(projectCode);
    }

5. 缓存更新策略

并不是所有的数据都一定被保存在缓存之中,会保存在缓存中的数据一般都属于热点数据,所有的热点数据一般都是由客户进行维护的(用户可能是普通的使用者,也有可能是推手),但是也考虑到数据修改的问题,在SpringCache之中是允许使用者进行缓存数据更新的。

友情提示:非必要不更新。
在缓存之中保存的数据内容,如果不是特别有需要的时候,千万不要进行更新操作,因为有可能造成缓存热点数据的失效,从而导致数据库之中的查询压力激增,最终导致系统崩溃。

SpringCache作为一款优秀的缓存组件,它在设计的时候已经考虑到所有数据更新的问题,支持有更新操作,而且这种更新的操作一般也是和业务有直接联系的。

在这里插入图片描述

  1. 更新缓存代码实现

注:在缓存里面JakartaEE提供有一个缓存数据操作的标准,这个标准版本号为“JSR-107”,SpringCache支持有该标准。

  //编辑雇员
    @CachePut(cacheNames = "emp", key = "#emp.eid", unless = "#result==null")
    public Emp edit(Emp emp) {
        return empDao.edit(emp);
    }
 @Test
    void testEditCache() {
        Emp emp1 = empService.get("1");
        log.info("[第一次查询],emp1:{}", emp1);

        Emp emp = Emp.builder()
                .eid("1")
                .job("经理更新")
                .ename("张三更新")
                .salary(1111.0)
                .build();

        empService.edit(emp);

        Emp emp2 = empService.get("1");
        log.info("[第二次查询],emp2:{}", emp2);
    }
[持久层],eid:1, emp:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[第一次查询],emp1:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[持久层],edit:Emp(eid=1, ename=张三更新, job=经理更新, salary=1111.0)
[第二次查询],emp2:Emp(eid=1, ename=张三更新, job=经理更新, salary=1111.0)

此时的程序代码执行完成之后,可以发现在第一次查询之后,由于缓存之中已经存在有了指定的数据项,所以在进行更新的时候,除了要进行数据表内容的修改之外,也需要进行进行缓存数据的更新处理,所以当第二次发出查询指令的时候,得到的就是缓存之中的新数据项。

这种缓存的更新操作其实并没有发生另外一次的数据查询(按照基本的做法,缓存数据修改应该先删除,然后进行查询,并且存放新的缓存数据),但是现阶段仅仅是在缓存内容上做了更新的处理,这一点作为缓存来讲已经足够了,但是考虑到性能问题,在高并发情况下一般还是不建议修改缓存数据。

6. 缓存清除策略

按照常规的理解,缓存的数据应该与数据库之中的实体数据相对应,所以当数据库之中的数据被删除之后,对应的缓存数据理论上也应该被删除。SpringCache考虑到数据删除的问题,提供有缓存的清除操作。

实际上很多的系统,可能是缓存还在但是数据已经不存在了,因为缓存的更新相对比数据的更新慢,同时放在缓存中的很多数据一般不会轻易改变。

在这里插入图片描述

如果想要实现这种缓存的清除操作,可以使用一个@CacheEvict注解完成,该注解使用的形式和@CachePut注解的形式类似,只需要设置一些删除条件即可。例如:当前是根据雇员的编号进行了数据的缓存配置,那么删除的时候只需要设置上同样的雇员编号即可。

package com.personal.caffeine.springcache.service;

import com.personal.caffeine.springcache.dao.IEmpDao;
import com.personal.caffeine.springcache.po.Emp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
@CacheConfig(cacheNames = "emp")//配置公共的缓存信息
public class IEmpService {

    @Autowired
    private IEmpDao empDao;

    //编辑雇员
    @CachePut(key = "#emp.eid", unless = "#result==null")
    public Emp edit(Emp emp) {
        return empDao.edit(emp);
    }

    //删除雇员信息
    @CacheEvict(key = "#eid")
    public boolean delete(String eid) {
        return empDao.delete(eid);
    }

    //根据id查询雇员信息
    @Cacheable()
    public Emp get(String eid) {
        return empDao.get(eid);
    }

    //根据名称查询雇员信息
    @Cacheable()
    public Emp getEname(String ename) {
        return empDao.getEname(ename);
    }
    
}

注意:@CacheConfig(cacheNames = “emp”)//配置公共的缓存信息

	@Test
    void testDeleteCache() {
        Emp emp1 = empService.get("1");
        log.info("[第一次查询],emp1:{}", emp1);

        Emp emp2 = empService.get("1");
        log.info("[第二次查询],emp2:{}", emp2);

        empService.delete("1");

        Emp emp3 = empService.get("1");
        log.info("[第三次查询],emp3:{}", emp3);

    }
[持久层],eid:1, emp:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[第一次查询],emp1:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[第二次查询],emp2:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[持久层],delete:1
[持久层],eid:1, emp:Emp(eid=1, ename=张三, job=经理, salary=1000.0)
[第三次查询],emp3:Emp(eid=1, ename=张三, job=经理, salary=1000.0)

在进行缓存数据的删除之后,除了数据表的数据被清除之外,对于缓存的内容也一并删除了,所以SpringCache在进行缓存的同步处理上确实优秀于其他的操作组件。

在并发量小的情况下,各种缓存的操作维护可以随意去搞,但是一旦到了高并发的应用场景,这种操作的方式一定不要轻易使用,因为缓存的更新有可能暴露终端数据的操作。

7. 多级缓存策略

对于当前的业务层来讲,service中提供了两个数据的查询操作,一个是根据id查询,还有一个是根据名称查询

//编辑雇员
    @CachePut(key = "#emp.eid", unless = "#result==null")
    public Emp edit(Emp emp) {
        return empDao.edit(emp);
    }

    //根据id查询雇员信息
    @Cacheable()
    public Emp get(String eid) {
        return empDao.get(eid);
    }

    //根据名称查询雇员信息
    @Cacheable()
    public Emp getEname(String ename) {
        return empDao.getEname(ename);
    }

我们首先执行下面的测试代码

 @Test
    void testEditCacheByName() {
        Emp emp1 = empService.getEname("李四");
        log.info("[第一次查询],emp1:{}", emp1);

        Emp emp = Emp.builder()
                .eid("1")
                .job("总监更新")
                .ename("李四")
                .salary(1111.0)
                .build();

        empService.edit(emp);

        Emp emp2 = empService.getEname("李四");
        log.info("[第二次查询],emp2:{}", emp2);
    }

执行结果

[持久层],ename:李四, emp:Emp(eid=1, ename=李四, job=总监, salary=20000.0)
[第一次查询],emp1:Emp(eid=1, ename=李四, job=总监, salary=20000.0)
[持久层],edit:Emp(eid=1, ename=李四, job=总监更新, salary=1111.0)
[第二次查询],emp2:Emp(eid=1, ename=李四, job=总监, salary=20000.0)

在当前默认的情况下,edit方法之中的缓存更新操作是以雇员id为主的,但是如果说现在根据姓名查询的时候,这个更新操作可能无法针对于根据姓名的缓存更新。

通过当前的操作结果可以发现,此时调用了edit方法之后,仅仅实现的是数据表的修改,但是并没有进行缓存数据的更新,所以当再次根据姓名进行数据查询的时候,所查询到的只是缓存之中的旧数据,不更新的原因非常简单,就是因为当前的edit方法并没有配置根据名称更新的缓存处理。

修改service服务,使其可以实现多级缓存更新配置。

@Caching(put = {
            @CachePut(key = "#emp.eid", unless = "#result==null"),
            @CachePut(key = "#emp.ename", unless = "#result==null")
    })
    public Emp edit(Emp emp) {
        return empDao.edit(emp);
    }

执行结果

[持久层],ename:李四, emp:Emp(eid=1, ename=李四, job=总监, salary=20000.0)
[第一次查询],emp1:Emp(eid=1, ename=李四, job=总监, salary=20000.0)
[持久层],edit:Emp(eid=1, ename=李四, job=总监更新, salary=1111.0)
[第二次查询],emp2:Emp(eid=1, ename=李四, job=总监更新, salary=1111.0)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值