SpringBoot-Cache-入门与基本使用

1. SpringBoot对缓存的支持

JSR-107 定义了5个核心接口来实现缓存操作,分别是CachingProvider, CacheManager, Cache, Entry和Expiry。

Spring从3.1开始定义了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用 JSR-107 注解简化我们开发;

其中,Spring支持多种缓存的实现方式,如下图所示:

2. 核心接口与缓存注解

Spring 中关于缓存的核心接口主要有两个:

  1. org.springframework.cache.Cache:用于定义缓存的各种操作
  2. org.springframework.cache.CacheManager :用于管理各个cache缓存组件

Spring中常用的关于缓存的注解有以下几个:

注解作用
@Cacheable通常用于配置方法,将方法的返回结果注入到缓存对象中
@CacheConfig用于对类进行配置,对整个类的缓存进行配置,可用 @Cacheable取代
@CacheEvict可用于类或方法,用于清空缓存
@CachePut强制执行方法并将返回结果放入缓存,而不是像 @Cacheable 那样首先从缓存中寻找方法返回结果是否存在缓存
@EnableCaching用于SpringBoot的启动类,开启注解功能

3. 缓存的基本使用

下面用一个小demo来实现一下Springboot中的缓存使用。

  1. 创建一个数据库和数据表,创建对应好实体类:
public class Employee {
	
	private Integer id;
	private String lastName;
	private String email;
	private Integer gender; 
	private Integer dId;
	
    //getter和setter方法省略
}
  1. 本例采用Mybatis框架,采用注解的方式编写crud的mapper接口实现DAO层:
@Mapper
public interface EmployeeMapper {

    @Select("SELECT * FROM employee WHERE id = #{id}")
    public Employee getEmpById(Integer id);

    @Update("UPDATE employee SET lastName=#{lastname}, email=#{email}, gender=#{gender}, d_id=#{dId} WHRER id=#{id}")
    public void updateEmp(Employee emp);

    @Delete("DELETE FROM employee WHERE id=#{id}")
    public void deleteEmpById(Integer id);


    @Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName}, #{email}, #{gender}, #{dId})")
    public void insertEmployee(Employee employee);

}
  1. 编写相应的service类和Controller类实现数据库的查询:
//Service
@Service
public class EmployeeService {

    @Resource	//用Autowired会出现无法获取mapper类的报错,可忽略
    EmployeeMapper employeeMapper;

    @Cacheable(cacheNames = {"emp"} )	//开启缓存,将查询结果放入到名为emp的cache组件中
    public Employee getEmp(Integer id){
        Employee employee = employeeMapper.getEmpById(id);
        return employee;
    }
}
//-------------------------------------------------------------------
//Controller
@RestController
public class EmployeeController {
    @Autowired
    EmployeeService employeeService;

    @RequestMapping("/emp/{id}")
    public Employee getEmployee(@PathVariable("id") Integer id){
        Employee employee = employeeService.getEmp(id);
        return employee;
    }
}
  1. 在启动类中加入注解开启缓存功能;
@MapperScan("com.erving.springboot01cache.mapper")
@SpringBootApplication
@EnableCaching
public class Springboot01CacheApplication {

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

下面来看一下缓存开启的效果:

没有开启缓存时,多次向服务器发送相同的查询请求,可以看到返回的json数据:

此时,每次请求都会导致服务器执行一次数据库查询,严重影响效率:

开启缓存后,只有第一次向服务器发送请求时,服务器执行了查询,并将查询结果放入缓存,此后再次执行查询便从缓存中获取查询结果。

4. Springboot缓存的基本原理

1. CacheConfigurationImportSelector

该方法导入了所有缓存类型的配置类,默认启用 SimpleCacheConfiguration

2. SimpleCacheConfiguration

SimpleCacheConfiguration 配置类向容器中注入了一个 ConcurrentMapCacheManager实例 。

3. ConcurrentMapCacheManager

该类用于管理缓存对象的实例,主要有如下几个方法:

//根据缓存对象的名称获取缓存对象	
public Cache getCache(String name) {
		Cache cache = this.cacheMap.get(name);
		if (cache == null && this.dynamic) {
			synchronized (this.cacheMap) {
				cache = this.cacheMap.get(name);
				if (cache == null) {
					cache = createConcurrentMapCache(name);
					this.cacheMap.put(name, cache);
				}
			}
		}
		return cache;
	}

//创建一个底层为ConcurrentMap的缓存实例对象
protected Cache createConcurrentMapCache(String name) {
		SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
		return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256),
				isAllowNullValues(), actualSerialization);

	}

//设置缓存名称
public void setCacheNames(@Nullable Collection<String> cacheNames) {
		if (cacheNames != null) {
			for (String name : cacheNames) {
				this.cacheMap.put(name, createConcurrentMapCache(name));
			}
			this.dynamic = false;
		}
		else {
			this.dynamic = true;
		}
	}
//...

4. ConcurrentMapCache

ConcurrentMapCacheManager 类会调用一个 ConcurrentMapCache 构造方法来创建一个 ConcurrentMapCache 缓存实例对象,这个类就是用来实现缓存的底层类了,看一下这个类的内容:

public class ConcurrentMapCache extends AbstractValueAdaptingCache {

	private final String name;
	//store对象是一个ConcorentMap实例,用来直接存放缓存对象
	private final ConcurrentMap<Object, Object> store;
	
    //从外部获取store对象
    	@Override
	public final ConcurrentMap<Object, Object> getNativeCache() {
		return this.store;
	}
	
    //根据传入的key值查询缓存对象
	@Override
	@Nullable
	protected Object lookup(Object key) {
		return this.store.get(key);
	}
	
    //放入缓存对象和对应的key值
	@Override
	public void put(Object key, @Nullable Object value) {
		this.store.put(key, toStoreValue(value));
	}
    //...等等
}
    

上面列举了该类的几个基本方法。

综上所述,Springboot的默认缓存实现主要由两个ConcurrentHashMap实现,一个是 ConcurrentMapCacheManager类中的 cacheMap,该对象存放着 不同名称的 Cache 对象。而另一个是 ConcurrentMapCache 类中的 store 对象,该对象存放着被放入到缓存中的各种类的对象。

5. 获取缓存中的所有对象

在第3节代码的基础上,新增以下代码:

//Department对象的pojo实体类代码忽略,只给出相关的查询代码
@RestController
public class DepartmentController {
    @Autowired
    DepartmentService departmentService;

    @RequestMapping("/dep/{id}")
    public Department getDepById(@PathVariable("id") Integer id){
        return departmentService.getDepById(id);
    }

}

@Service
public class DepartmentService {


    @Resource
    DepartmentMapper departmentMapper;
    @Cacheable(cacheNames = {"dep"} )
    public Department getDepById(Integer id){
        return departmentMapper.getDepById(id);
    }
}

@Mapper
public interface DepartmentMapper {

    @Select("SELECT * FROM department WHERE id = #{id}")
    public Department getDepById(Integer id);
}

在一个controller类中加入以下代码,可以实现对缓存数据的遍历:

    @Autowired	//注入缓存管理器
    CacheManager cacheManager;
    
    @RequestMapping("/showcache")
    public void showCache(){
        //获取cacheMap中所有缓存对象的名称
        Collection<String> e= cacheManager.getCacheNames();
        for (String s : e) {
            //遍历所有缓存对象
            Cache cache = cacheManager.getCache(s);
            System.out.println(cache.getName());
            //获取ConcurrentMapCache对象中的store实例对象,以ConcurrentMap形式返回
            ConcurrentMap<Object, Object> map = (ConcurrentMap<Object, Object>) cache.getNativeCache();
            Collection<Object> collection = map.values();
            //遍历缓存对象中的所有元素
            for (Object o : collection) {
                System.out.println(o);
            }
        }
    }
    

通过http请求在缓存中放入两个名为emp和dep的缓存对象,每个缓存对象都放入对应的Employee类对象和Department对象,再访问上述的路径"…/showcache",可以看到控制台中输出一下信息:

5. 基本注解

1. @Cacheable

该注解用于方法上,将方法返回值放入缓存中。

1. value/cachenames

用于指定缓存名称,为数组形式,可以给一个返回结果声明多个缓存对象,即放入多个缓存对象中。

2. key / keyGenerator

用于指定方法返回结果在缓存中存放时所对应的key值,默认情况下使用方法的参数生成。可以使用spel表达式自己配置,也可以创建一个keyGenerator类来生成自己的key值。

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uuWzEJbU-1586441822869)(img/Snipaste_2020-04-09_20-53-10.png)]

3. condition / unless

用于带条件地创建缓存,使用spel表达式,利用方法参数或返回结果进行条件设置,当满足条件时,才将结果放入缓存。

当unless指定条件为true时,不执行缓存。

4. sync

在多线程的情况下,可以设置sync=true来实现缓存的线程安全。当开启同步后,unless 关键字不能使用。

2. CachePut

使用该注解后,无论方法的返回值是否被缓存,方法都会执行,而且每次执行后的返回结果都会被更新到缓存中,因此常用于更新数据库内容的方法中。

其内置属性与Cacheable基本一致。

注意使用该注解时,key值需要与被更新的对象在缓存中的原有key值相对应,若key值不一致,将会创建一个新的缓存,无法实现更新效果。

3. CacheEvict

通过制定的key值清除对应的缓存,或者使用allEntries=true属性删除所有的缓存。

默认情况下,方法执行完毕后,才进行缓存清除,从而保证缓存与数据库的状态保持一致。若缓存不依赖于方法的执行结果,可以使用 beforeInvocation=true 来配置为方法执行前执行缓存清除。

4. Caching

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {

	Cacheable[] cacheable() default {};

	CachePut[] put() default {};

	CacheEvict[] evict() default {};

}

如代码所示,该注解用于组合三个常用的注解。

5. CacheConfig

该注解用于类中,当一个类中的多个方法中的缓存注解使用某些相同的属性时,为了简化代码,可以将这些属性在类的层级使用该注解进行配置,从而省略方法层级的配置。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值