Spring Cache组件

news2024/11/20 3:28:39

《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;//实现缓存数据存储的集合

}
  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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/62537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]计算机毕业设计基于Springboot景区直通车服务系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

探究商城中的在线语音谈判功能开发

近年来&#xff0c;电子商务已经成为一种新型的商业形式&#xff0c;电子商务的发展让传统的面对面谈判发展为以互联网为依托的在线沟通&#xff0c;相较于传统的谈判方式&#xff0c;在线语音谈判方式更便捷、更高效&#xff0c;逐渐成为商务谈判的主流&#xff0c;因此该功能…

FastReport Mono 2023.1 Crack

与位于 FastReport 云服务器上的报表进行交互。特征 现在支持与 FastReport Cloud 报告生成器的部分集成。改进的报告验证器包括一个带有错误编号的新的可自定义列。添加了来自 JasperReports 的模板转换器。改进的“MSChartObject”&#xff1a; 常用设置已移至单独的编辑器。…

线程池详细介绍

线程池执行流程 线程池的状态 RUNNINGSHUTDOWNSTOPTIDYINGTERMINATED 线程池优雅关闭 线程池有两个关闭方法&#xff0c;shutdown()和shutdownNow()&#xff0c;shutdown()切换到SHUTDOWN状态&#xff0c;shutdownNow()切换到STOP状态&#xff0c;当队列和线程池都为空的时候…

学习 | ANSYS经典界面在压力容器分析设计中的应用

导读&#xff1a;分析设计作为压力容器设计的重要方法&#xff0c;不仅解决了压力容器常规设计所不能解决的问题&#xff0c;而且也是压力容器设计观点与方法上的一个质的飞跃。 分析设计通常分为两类&#xff1a;应力分类法&#xff08;弹性方法&#xff09;和直接法&#xf…

基于FPGA的SD NAND图片显示实现

文章目录 0、前言 1、目标 2、图片的预处理 3、SD NAND的预处理 4、FPGA实现 4.1、详细设计 4.2、仿真 4.3、实验结果 0、前言 在上一篇文章《基于FPGA的SD卡的数据读写实现&#xff08;SD NAND FLASH&#xff09;》中&#xff0c;我们了解到了SD NAND Flash的相关知识…

大(json)文件压缩(minify)

文章目录Preface解决方案Preface 现在在做一个 GIS 地图的项目, 做过地图的应该就知道各省/市/县的json文件有多大(大部分都是经纬度数据), 就直接放前台public目录下了. 文件过大, 上传到服务器就占用很多空间, 这时候就有人提出需求, 让把这个问题处理一下. (虽然这个事情没…

mysql日志管理 、备份与恢复

目录 一、数据备份的重要性与分类 1、数据备份的重要性 2、从物理与逻辑的角度&#xff0c;备份分为 3、从数据库的备份策略角度&#xff0c;备份可分为 3.1 完全备份&#xff08;只适合第一次&#xff09; 3.2 差异备份&#xff08;用的较少&#xff0c;有丢失数据的现象…

应用 Serverless 化,让业务开发心无旁骛

我们希望让用户做得更少而收获更多&#xff0c;通过Serverless化&#xff0c;用云就像用电一样简单。”张建锋表示&#xff0c;Serverless 让云计算从一种资源真正变成一种能力&#xff0c;未来云将全面 Serverless 化&#xff0c;更加接近“电网”模式&#xff0c;按计算的调用…

使用Visual Studio Code 进行Python编程

1、下载Visual Studio Code 到微软的Visual Studio Code官方主页下载Visual Studio Code: Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器Visual Studio 开发工具和服务让任何开发人员在任何平台和语言的应用开发都更加轻松。 随时随地免费使用代码编辑器或 I…

web期末网站设计大作业:基于HTML+CSS+JavaScript制作新能源汽车企业网站

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

负载均衡四层和七层的区别

一. 什么是负载均衡 1&#xff09;负载均衡&#xff08;Load Balance&#xff09;建立在现有网络结构之上&#xff0c;它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。负载均衡有两方面的含义&#…

基于Boost库的在线搜索引擎

文章目录Boost库搜索引擎1. 项目背景2. 宏观原理3. 搜索引擎技术栈和项目环境4.正排索引vs倒排索引&#xff08;index.hpp&#xff09;1. 正排索引:数组vector<>2. 目标文档进行分词(方便倒排索引和查找)3. 倒排索引&#xff1a;unordered_map<>模拟一次查找的过程…

反向传播不香了?解读 Hinton 大佬的 Forward-Forward 算法

今天解读一篇Hinton大佬最近分享的论文。 在最近的NeurIPS2022会议上&#xff0c;图灵奖得主Hinton作为演讲嘉宾&#xff0c;分享了一个题为《The Forward-Forward Algoritm: Some Preliminary Investigations》的论文。 该论文提出了一种取代反向传播的前向-前向传播的训练方…

华为机试 - 比较两个版本号的大小

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 输入两个版本号 version1 和 version2&#xff0c;每个版本号由多个子版本号组成。 子版本号之间由 “.” 隔开&#xff0c;由大小写字母、数字组成&#xff0c;并且至少有一个字符。 按从左到右的顺…

spirng boot 打包,胖fat包和瘦thin包

一、打胖包fat 打胖包采用的是spring的标准来执行&#xff0c;所以使用的是spring boot提供的打包插件 参考来源&#xff1a;打包Spring Boot应用 - 廖雪峰的官方网站 步骤 pom配置 <project ...>...<build><plugins><plugin><groupId>org.s…

Python 奇淫技巧,助你更好的摸鱼

作为一个数据分析者&#xff0c;日常工作几乎离不 python。一路走来&#xff0c;积累了不少有用的技巧和 tips&#xff0c;现在就将这些技巧分享给大家。这些技巧将根据其首字母按 A-Z 的顺序进行展示。 ALL OR ANY Python 之所以成为这么一门受欢迎的语言一个原因是它的可读…

(一)整合管理范围管理

爬虫组件分析目录概述需求&#xff1a;设计思路实现思路分析1.指定项目章程2.项目管理计划3.指导4。管理项目知识4.5.监控项目工作4.6 实施整体变更控制4.7 项目收尾合同5。范围管理收集需求定义范围创建WBS确认范围&#xff1b;控制范围Survive by day and develop by night. …

Redis内存耗尽后会发生什么?

Redis内存耗尽后会发生什么?前言设置有效期过期策略8 种淘汰策略LRU 算法Redis 如何管理热度数据LFU 算法访问频次递增访问频次递减前言 作为一台服务器来说&#xff0c;内存并不是无限的&#xff0c;所以总会存在内存耗尽的情况&#xff0c;那么当 Redis 服务器的内存耗尽后…

什么是Spring的AOP功能?

如果说 IOC 是 Spring 的核心&#xff0c;那么面向切面编程AOP就是 Spring 另外一个最为重要的核心。 AOP的定义 AOP &#xff08;Aspect Orient Programming&#xff09;,直译过来就是 面向切面编程,AOP 是一种编程思想&#xff0c;是面向对象编程&#xff08;OOP&#xff0…