您的位置:首页 > 编程语言 > Java开发

SpringBoot25-spingboot数据访问-数据缓存Cache

2017-10-08 22:03 525 查看
    我们知道一个程序的瓶颈在于数据库,我们也知道内存的速度是大大快于硬盘的速度的。当我们需要重复地获取相同的数据的时候,我们一次又一次的请求数据库或者远程服务,导致大量的时间耗费在数据库查询或者远程方法调用上,导致程序性能的恶化,这便是数据缓存要解决的问题。

一,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,例如:
@Bean
public 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 2017/10/9
*/
@Configuration
@EnableCaching
public 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="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/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连接驱动-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.39</version>
</dependency>

<!-- https://mvnrepository.com/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 2017/10/3
*/
//@Entity注解指明这是一个和数据库表映射的实体类
@Entity
public 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 2017/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 2017/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 2017/10/9
*/
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
private PersonRepository personRepository;
/**
* 注意:如果没有指定key,则方法参数作为key保存到缓存中
*/
/**
*@CachePut缓存新增的或更新的数据到缓存,其中缓存的名称为people,数据的key是person的id
* @param person
* @return
*/
@CachePut(value = "people",key="person.id")
@Override
public 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")
@Override
public 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")
@Override
public 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 2017/10/9
*/
@RestController
@RequestMapping("cache")
public class CacheController {
@Autowired
private 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
@EnableCaching
public class Springboot8cacheApplication {

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

7,运行

     当我们对数据库做了缓存以后,数据的获得将从缓存中得到,而不是从数据库中得到。当前的数据库的数据情况如下所示:



    我们在每次运行测试情况下,都重启了应用程序。
1)测试@Cacheable
      第一次访问http://localhost:9090/cache/cacheable?id=85,第一次将调用方法查询数据库,并将数据放到缓存people中。
此时控制台输出如下:
2017-10-09 23:29:05.877 INFO 2468 --- [nio-9090-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-10-09 23:29:05.878 INFO 2468 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-10-09 23:29:05.939 INFO 2468 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 61 ms
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=?
为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=深圳深圳,此时控制台输出如下:
2017-10-09 23:37:10.823 INFO 1212 --- [nio-9090-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-10-09 23:37:10.824 INFO 1212 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-10-09 23:37:10.893 INFO 1212 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 69 ms
Hibernate: 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的依赖即可:
<!-- https://mvnrepository.com/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的依赖即可:
<!-- https://mvnrepository.com/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,只需添加下面的依赖即可:
<!-- https://mvnrepository.com/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。

源码地址:https://github.com/wj903829182/SpringCloudTwo/tree/master/springboot8cache
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息