《Spring Cache组件》
提示: 本材料只做个人学习参考,不作为系统的学习流程,请注意识别!!!
《Spring Cache组件》
- 《Spring Cache组件》
- 1. Spring Cache组件概述
- 2. ConcurrentHashMap缓存管理
- 3. @Cacheable详解
- 4. Caffeine缓存管理
- 5. 缓存更新策略
- 6. 缓存清除策略
- 7. 多级缓存策略
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;//实现缓存数据存储的集合
}
- 创建一个自定义的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上下文数据
- 在进行缓存的时候将雇员编号作为缓存的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)
- 默认情况下只要进行了数据的查询,那么肯定所有的数据都要求进行缓存处理了,可是现在要求对缓存的数据追加一些标记,判断查询的参数里面包含有指定的字符串才允许缓存。
//根据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)
- 将雇员查询结果中工资低于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)
业务层之中的缓存是针对所有数据的,但是如果某些数据在设计的时候没有达到缓存的要求,就将其进行缓存了,这样会造成缓存数据的污染,从而产生严重的缓存处理性能。
- 如果说现在是在多线程的处理环境下进行了缓存的查询,有可能会造成缓存穿透问题。现在假设有三个线程查询数据,但是三个线程判断的时候缓存都不存在,所以必然会发生三个线程同时查询数据库操作的可能,此时可以考虑同步缓存操作。
//根据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缓存管理类。
- 写法一:修改配置类,采用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)
- 写法二:修改配置类,采用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作为一款优秀的缓存组件,它在设计的时候已经考虑到所有数据更新的问题,支持有更新操作,而且这种更新的操作一般也是和业务有直接联系的。
- 更新缓存代码实现
注:在缓存里面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)