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

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

一,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

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值