我们知道一个程序的瓶颈在于数据库,我们也知道内存的速度是大大快于硬盘的速度的。当我们需要重复地获取相同的数据的时候,我们一次又一次的请求数据库或者远程服务,导致大量的时间耗费在数据库查询或者远程方法调用上,导致程序性能的恶化,这便是数据缓存要解决的问题。
一,Spring 缓存支持
Spring定义了org.springframework.cache.CacheManager和org.springframework.cache.Cache接口用来统一不同的缓存技术。其中CacheManager是Spring提供的各种缓存技术抽象接口,Cache接口包含缓存的各种操作(增加,删除,获得缓存,我们一般不会直接和此接口打交道)
1,Spring支持的CacheManager
针对不同的缓存技术,需要实现不同的CacheManager,Spring定义了如下所示的CacheManager实现:
SimpleCacheManager:使用简单的Collection来存储缓存,主要用来测试用途。
ConcurrentMapCacheManager:使用ConcurrentMap来存储缓存。
NoOpCacheManager:仅测试用途,不会实际存储缓存。
EhCacheCacheManager:使用EhCache作为缓存技术。
GuavaCacheManager:使用Google Guava的GuavaCache作为缓存技术。
HazelcastCacheManager:使用Hazelcast作为缓存技术。
JCacheCacheManager:支持JCache标准的实现作为缓存技术,如Apache Commons JCS
RedisCacheManager:使用Redis作为缓存技术。
在我们使用任意一个实现的CacheManager的时候,需要注册实现CacheManager的Bean,例如:
@Beanpublic EhCacheCacheManager cacheManager(CacheManager ehCacheCacheManager){return new EhCacheCacheManager(ehCacheCacheManager);}
当然,每种缓存技术都有很多的额外配置,但配置cacheManager是必不可少的
2,声名式缓存注解
Spring提供了4个注解来声明缓存规则(又是使用注解式的AOP的一个生动例子)。这四个注解如下:
@Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放进缓存。
@CachePut:无论怎样,都会将方法的返回值放到缓存中。@CachePut的属性与@Cacheable保持一致。
@CacheEvict:将一条或多条数据从缓存中删除。
@Caching:可以通过@Caching注解组合多个注解策略在一个方法上。
@Cacheable,@Cacheable,@CacheEvict都有value属性,指定的是要使用的缓存名称;key属性指定的是数据在缓存中的存储的键。
3,开启声名式缓存支持
开启声名式缓存支持十分简单,只需在配置类上使用@EnableCaching注解即可,如下:
package com.jack.springboot8cache.config;import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.annotation.Configuration;/*** create by jack /10/9*/@Configuration@EnableCachingpublic class AppConfig {}
二,Spring Boot的支持
在spring中使用缓存技术的关键是配置CacheManager,而Spring Boot为我们自动配置了多个CacheManager的实现。
Spring Boot的CacheManager的自动配置放置在org.springframework.boot.autoconfigure.cache包中,如下所示:
通过上图我们可以看出,Spring Boot为我们自动配置了EhCacheCacheConfiguration(使用EhCache),GenericCacheConfiguration(使用Collection),GuavaCacheConfiguration(使用Guava),HazelcastCacheConfiguration(使用Hazelcast),InfinispanCacheConfiguration(使用Infinispan),JCacheCacheConfiguration(使用JCache),NoOpCacheConfiguration(不使用存储),RedisCacheConfiguration(使用Redis),SimpleCacheConfiguration(使用ConcurrentMap)。在不做任何额外配置的情况下,默认使用的是SimpleCacheConfiguration,即使用ConcurrentMapCacheManager。Spring boot支持以“spring.cache”为前缀的属性来配置缓存:
spring.cache.type=#可选generic,ehcache,hazelcast,infinispan,jcache,redis,guava,simple,none
spring.cache.cache-names=#程序启动时创建缓存名称
spring.cache.ehcache.config=#ehcache配置文件地址
spring.cache.hazelcast.config=#hazelcast配置文件地址
spring.cache.infinispan.config=#infinispan配置文件地址
spring.cache.jcache.config=#jcache配置文件地址
spring.cache.jcache.provider=#当多个jcache实现在类路径中的时候,指定jcache实现
spring.cache.guava.spec=#guava specs
在Spring Boot环境下,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在配置类上使用@EnableCaching开启缓存即可。
三,实战
下面将以Spring Boot默认的ConcurrentMapCacheManager作为缓存技术,演示@Cacheable,@CachePut,@CacheEvit,最后使用EhCache,Guava来替换缓存技术。
1,新建Spring Boot项目
新建Spring Boot项目依赖为Cache,JPA,WEB,添加mysql驱动,pom.xml的配置文件如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.jack</groupId><artifactId>springboot8cache</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>springboot8cache</name><description>Demo project for Spring Boot</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.7.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mysql连接驱动--><!-- /artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- /artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.39</version></dependency><!-- /artifact/org.springframework.boot/spring-boot-starter-cache --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
2,实体类
package com.jack.springboot8cache.entity;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;/*** create by jack /10/3*///@Entity注解指明这是一个和数据库表映射的实体类@Entitypublic class Person {/*** 主键id* @Id注解指明这个属性映射为数据库的主键* @GeneratedValue定义主键生成的方式,下面采用的是mysql的自增属性*/@Id@GeneratedValue(strategy= GenerationType.AUTO)private Integer id;/*** 姓名*/private String name;/*** 年龄*/private Integer age;/*** 地址*/private String address;public Person() {super();}public Person(Integer id, String name, Integer age, String address) {super();this.id = id;this.name = name;this.age = age;this.address = address;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}}
3,实体类Repository
package com.jack.springboot8cache.dao;import com.jack.springboot8cache.entity.Person;import org.springframework.data.jpa.repository.JpaRepository;/*** create by jack /10/8* 实体类*/public interface PersonRepository extends JpaRepository<Person,Integer> {}
4,业务服务
1)接口:
package com.jack.springboot8cache.service;import com.jack.springboot8cache.entity.Person;/*** create by jack /10/9*/public interface DemoService {Person save(Person person);void remove(Integer id);Person findOne(Person person);}
2)实现类
package com.jack.springboot8cache.impl;import com.jack.springboot8cache.dao.PersonRepository;import com.jack.springboot8cache.entity.Person;import com.jack.springboot8cache.service.DemoService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;/*** create by jack /10/9*/@Servicepublic class DemoServiceImpl implements DemoService {@Autowiredprivate PersonRepository personRepository;/*** 注意:如果没有指定key,则方法参数作为key保存到缓存中*//***@CachePut缓存新增的或更新的数据到缓存,其中缓存的名称为people,数据的key是person的id* @param person* @return*/@CachePut(value = "people",key="person.id")@Overridepublic Person save(Person person) {Person p = personRepository.save(person);System.out.println("为id,key为:"+p.getId()+"数据做了缓存");return p;}/*** @CacheEvict从缓存people中删除key为id的数据* @param id*/@CacheEvict(value = "people")@Overridepublic void remove(Integer id) {System.out.println("删除了id,key为"+id+"的数据缓存");personRepository.delete(id);}/*** @Cacheable缓存key为person的id数据到缓存people中* @param person* @return*/@Cacheable(value = "people",key = "person.id")@Overridepublic Person findOne(Person person) {Person p = personRepository.findOne(person.getId());System.out.println("为id,key为:"+p.getId()+"数据做了缓存");return p;}}
注意:如果没有指定key,则方法参数作为key保存到缓存中
5,控制器
package com.jack.springboot8cache.controller;import com.jack.springboot8cache.entity.Person;import com.jack.springboot8cache.service.DemoService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/*** create by jack /10/9*/@RestController@RequestMapping("cache")public class CacheController {@Autowiredprivate DemoService demoService;@RequestMapping("/put")public Person put(Person person){return demoService.save(person);}@RequestMapping("/evit")public String evit(Integer id){demoService.remove(id);return "ok";}@RequestMapping("/cacheable")public Person cacheable(Person person){return demoService.findOne(person);}}
6,开启缓存支持
package com.jack.springboot8cache;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication@EnableCachingpublic class Springboot8cacheApplication {public static void main(String[] args) {SpringApplication.run(Springboot8cacheApplication.class, args);}}
7,运行
当我们对数据库做了缓存以后,数据的获得将从缓存中得到,而不是从数据库中得到。当前的数据库的数据情况如下所示:
我们在每次运行测试情况下,都重启了应用程序。
1)测试@Cacheable
第一次访问http://localhost:9090/cache/cacheable?id=85,第一次将调用方法查询数据库,并将数据放到缓存people中。
此时控制台输出如下:
-10-09 23:29:05.877 INFO 2468 --- [nio-9090-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'-10-09 23:29:05.878 INFO 2468 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started-10-09 23:29:05.939 INFO 2468 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 61 msHibernate: select person0_.id as id1_0_0_, person0_.address as address2_0_0_, person0_.age as age3_0_0_, person0_.name as name4_0_0_ from person person0_ where person0_.id=?为id,key为:85数据做了缓存
页面输出如下:
再次访问http://localhost:9090/cache/cacheable?id=85,此时控制台没有再次输出hibernate的查询语句,以及“为id,key为:85数据做了缓存”字样,表示没有调用这个方法,页面直接从数据缓存中获得数据,页面输出结果如下:
2)测试@CachePut
访问http://localhost:9090/cache/put?name=jack8&age=99&address=深圳深圳,此时控制台输出如下:
-10-09 23:37:10.823 INFO 1212 --- [nio-9090-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'-10-09 23:37:10.824 INFO 1212 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started-10-09 23:37:10.893 INFO 1212 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 69 msHibernate: insert into person (address, age, name) values (?, ?, ?)为id,key为:86数据做了缓存
页面输出如下:
我们再次访问:http://localhost:9090/cache/cacheable?id=87,控制台无输出,从缓存直接获得数据,页面显示如上。
3)测试@CacheEvit
访问:http://localhost:9090/cache/cacheable?id=87,为id为87的数据做缓存,再次访问http://localhost:9090/cache/cacheable?id=87,确认数据已经从缓存中获取。
访问:http://localhost:9090/cache/evit?id=87,从缓存中删除key为87的缓存数据:
删除了id,key为87的数据缓存Hibernate: select person0_.id as id1_0_0_, person0_.address as address2_0_0_, person0_.age as age3_0_0_, person0_.name as name4_0_0_ from person person0_ where person0_.id=?Hibernate: delete from person where id=?
再次访问:http://localhost:9090/cache/cacheable?id=87,观察控制台,数据已经从数据库中删除了,查询缓存,发现空指针异常了,查询失败
四,切换缓存技术
切换缓存技术除了移入相关依赖包或者配置以外,使用方式和上面的实战例子保存一致。下面讲解Spring Boot下EhCache和Guava作为缓存技术的方式,其余缓存技术也是类似的方式。
1,EhCahce
当我们需要使用EhCache作为缓存技术的时候,我们只需要在pom.xml中添加EhCache的依赖即可:
<!-- /artifact/net.sf.ehcache/ehcache --><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><version>2.10.4</version></dependency>
EhCache所需的配置文件ehcache.xml只需放在类路径下,Spring Boot会自动扫描,例如:
<?xml version="1.0" encoding="UTF-8">
<ehcache>
<cache name="people" maxElementsInMemory="1000"/>
</ehcache>
Spring Boot会给我们自动配置EhcacheCacheManager的Bean。
2,Guava
当我们需要使用Guava作为缓存技术的时候,我们只需要在pom.xml中增加Guava的依赖即可:
<!-- /artifact/com.google.guava/guava --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>23.0</version></dependency>
Spring Boot会给我们自动配置GuavaCacheManager这个Bean。
3,Redis
使用Redis,只需添加下面的依赖即可:
<!-- /artifact/org.springframework.boot/spring-boot-starter-data-redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>1.5.7.RELEASE</version></dependency>
Spring Boot将会为我们自动配置RedisCacheManager以及RedisTemplate的Bean。
源码地址:/wj903829182/SpringCloudTwo/tree/master/springboot8cache
如果觉得《SpringBoot25-spingboot数据访问-数据缓存Cache》对你有帮助,请点赞、收藏,并留下你的观点哦!