https://docs.spring.io/spring-framework/docs/5.3.8-SNAPSHOT/reference/html/integration.html#cache
自Spring 3.1版本以来,Spring框架支持低侵入的方式向已有Spring应用加入缓存特性。与声明式事务类似,声明式缓存Spring Cache抽象允许一致的API来已支持多种不同的缓存解决方案,同时将对代码的影响减少到最小。从Spring 4.1开始,Spring已完整支持JSR-107注解和更多的定制选项。
JSR-107与Spring 自带注解对比
-
@Cacheable / @CacheResult 用于读取和设置缓存
-
@CachePut 添加/更新缓存
-
@CacheEvict / @CacheRemove 移除缓存
区分Cache与Buffer
很多情况下,Buffer(缓冲)与Cache(缓存)是类似的。然而在表现形式与应用场景上两个的差别还是比较明显的。
传统意义上,Buffer作为快速实体与慢速实体之间的桥梁。比如:硬盘上的文件数据会先到内存,再被CPU加载,内存作为Buffer可以减少等待时间,同时利用Buffer可将原本小块数据攒成整块一次性交给处理者,可以有效减少IO。此外,通常至少有一个对象对其可见。
而Cache缓存,相对来说是隐藏的,对于访问者与被访问者来说应该是隐藏的,好的程序设计可以让使用者对缓存无感知,同时它还可以提高性能,允许应用多次、快速的读取缓存数据。
如何使用Spring Cache
在启动类使用@EnableCaching注解中开启缓存
@SpringBootApplication @EnableCaching public class RedisDemoApplication { public static void main(String[] args) { SpringApplication.run(RedisDemoApplication.class, args); } }
创建配置类:想配置就自己配置,不配置也可以, 如果没有配置,最后异常,那么写上该配置试试,不行的话,就是代码问题
@Configuration public class SpringCacheConfgiration { @Bean @Primary //设置默认的CacheManager public CacheManager cacheManager(LettuceConnectionFactory factory){ //加载默认Spring Cache配置信息 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); //设置有效期为1小时 config = config.entryTtl(Duration.ofHours(1)); //说明缓存Key使用单冒号进行分割 config = config.computePrefixWith(cacheName -> cacheName + ":"); //Redis Key采用String直接存储 config = config.serializeKeysWith(RedisSerializationContext .SerializationPair .fromSerializer(new StringRedisSerializer())); //Redis Value则将对象采用JSON形式存储 config = config.serializeValuesWith(RedisSerializationContext .SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())); //不缓存Null值对象 config = config.disableCachingNullValues(); //实例化CacheManger缓存管理器 RedisCacheManager cacheManager = RedisCacheManager.RedisCacheManagerBuilder .fromConnectionFactory(factory) //绑定REDIS连接工厂 .cacheDefaults(config) //绑定配置对象 .transactionAware() //与声明式事务注解@Transactional进行兼容 .build(); //完成对象构建 return cacheManager; } }
创建实体类:
@Data public class Emp implements Serializable { private Integer empno; private String name; private Date birthday; private Float salary; private String department; }
创建EmpMapper:
@Repository public class EmpDao {d public Emp findById(Integer empId){ System.out.println("执行了findById方法:EmpId:" + empId); return new Emp(empId , "张三" , new Date() , 1000f ,"研发部"); } public List<Emp> selectByParams(){ System.out.println("已执行selectByPrams方法"); List list = new ArrayList(); for(int i = 0 ; i < 10 ; i++) { list.add(new Emp(i , "emp" + i , new Date() , 5000 + i * 100f , "RESEARCH")); } return list; } public Emp insert(Emp emp){ System.out.println("正在创建" + emp.getEmpno() + "员工信息"); return emp; } public Emp update(Emp emp){ System.out.println("正在更新" + emp.getEmpno() + "员工信息"); return emp; } public void delete(Integer empno){ System.out.println("删除" + empno + "员工信息"); } }
创建Service:
@Service public class EmpService { @Resource EmpDao empDao; @Cacheable(value = "emp", key ="#empId", condition = "#empId != 1000") public Emp findById(Integer empId) { return empDao.findById(empId); } @Cacheable(value = "emp:rank:salary") public List<Emp> getEmpRank() { return empDao.selectByParams(); } @CachePut(value = "emp", key = "#emp.empno") public Emp create(Emp emp) { return empDao.insert(emp); } @CachePut(value = "emp", key = "#emp.empno") public Emp update(Emp emp) { return empDao.update(emp); } @CacheEvict(value="emp" , key = "#empno") public void delete(Integer empno) { empDao.delete(empno); } }
创建测试类:
@SpringBootTest public class SpringCacheTestor { @Resource private EmpService empService; @Test public void testFindById(){ Emp emp1 = empService.findById(1001); System.out.println(emp1.getName()); emp1 = empService.findById(1001); System.out.println(emp1.getName()); Emp emp2 = empService.findById(1000); System.out.println(emp2.getName()); emp2 = empService.findById(1000); System.out.println(emp2.getName()); } @Test public void testEmpRank() { List<Emp> list = empService.getEmpRank(); // 第一次执行getEmpRank()方法的返回值也会放入缓存 list = empService.getEmpRank(); // 第二次会先从缓存中获取,若没有则执行getEmpRank()方法 System.out.println("-----------------------------------------------"); for(Emp emp:list){ System.out.println(emp); } } @Test public void testCreate(){ empService.create(new Emp(1002 , "emp" + new Date().getTime() , new Date() , 1234f , "MARKET")); empService.create(new Emp(1002 , "emp" + new Date().getTime() , new Date() , 1235f , "MARKET")); empService.create(new Emp(1002 , "emp" + new Date().getTime() , new Date() , 1236f , "MARKET")); Emp emp = empService.findById(1002); System.out.println(emp); } @Test public void testUpdate(){ empService.update(new Emp(1002 , "u-emp" + new Date().getTime() , new Date() , 2234f , "MARKET")); empService.update(new Emp(1002 , "u-emp" + new Date().getTime() , new Date() , 3234f , "MARKET")); empService.update(new Emp(1002 , "u-emp" + new Date().getTime() , new Date() , 4234f , "MARKET")); Emp emp = empService.findById(1002); System.out.println(emp); } @Test public void testDelete(){ empService.delete(1002); //Emp emp = empService.findById(1002); //System.out.println(emp); } }
总结:
(1)要在启动类中添加@EnableCaching注解开启缓存,由于我们在配置文件中配置了redis,所以这里使用的redis作为缓存。 (2)@Cacheable注解放在service层中查询相关的方法上,若缓存中不存在对应的key则会执行service方法从数据库中查询数据,然后查询到的数据即会返回到上一层,也会在缓存中保存一份。若不满足查询条件condition则执行service方法去数据库查询数据。 (3)@CachePut注解放在service层中添加、修改相关的方法上,总结一句话:不管是放在添加还是修改的service方法上,若缓存中存在对应的key则对缓存中的数据进行修改,反之则会从缓存中添加。 (4)@CacheEvict注解则是根据key删除缓存中的数据