美文网首页我爱编程
Spring Boot进阶 之 Cache

Spring Boot进阶 之 Cache

作者: 诺之林 | 来源:发表于2018-05-17 23:49 被阅读231次

本文的示例代码参考CacheDemo

目录

开始

spring init -dweb,mysql,data-jpa,flyway,lombok --build gradle CacheDemo && cd CacheDemo

关于Flyway和DB Migration 详细参考Spring Boot开发 之 DB Migration

添加Model

vim src/main/java/com/example/CacheDemo/Product.java
package com.example.CacheDemo;

import lombok.Data;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;

@Entity
@Table(name = "products")
@Data
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @NotBlank
    private String productName;
}

关于Lombok更多介绍 可以参考Lombok简介

添加Service

vim src/main/java/com/example/CacheDemo/ProductRepository.java
package com.example.CacheDemo;

import org.springframework.data.repository.CrudRepository;

public interface ProductRepository extends CrudRepository<Product, Integer> {
}
vim src/main/java/com/example/CacheDemo/ProductService.java
package com.example.CacheDemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    public Iterable<Product> findAll() {
        return productRepository.findAll();
    }

    public Optional<Product> findById(Integer id) {
        return productRepository.findById(id);
    }
}

添加Controller

vim src/main/java/com/example/CacheDemo/ProductsController.java
package com.example.CacheDemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Optional;

@RestController
@RequestMapping("/products")
public class ProductsController {
    @Autowired
    private ProductService productService;

    @GetMapping
    public Iterable<Product> products() {
        return productService.findAll();
    }

    @GetMapping("/{productId}")
    public Optional<Product> product(@PathVariable("productId") String productId) {
        return productService.findById(Integer.valueOf(productId));
    }
}

数据库

数据库配置

vim src/main/resources/application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/cache?userSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

数据库服务

docker run --name cache-demo -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7.17

docker exec -i cache-demo mysql -uroot -p123456  <<< "CREATE DATABASE IF NOT EXISTS cache DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;"

数据库迁移

mkdir -p src/main/resources/db/migration

vim src/main/resources/db/migration/V1_0_0__create_product_table.sql
CREATE TABLE products (
  id bigint(20) NOT NULL AUTO_INCREMENT,
  product_name varchar(100) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY UK_product_name (product_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO products (product_name) VALUES ('Product01');

INSERT INTO products (product_name) VALUES ('Product02');

关于Flyway和DB Migration 详细参考Spring Boot开发 之 DB Migration

  • 测试
./gradlew bootrun
curl localhost:8080/products | json
[
  {
    "id": 1,
    "productName": "Product01"
  },
  {
    "id": 2,
    "productName": "Product02"
  }
]
curl localhost:8080/products/1 | json
{
  "id": 1,
  "productName": "Product01"
}
curl localhost:8080/products/2 | json
{
  "id": 2,
  "productName": "Product02"
}

这里使用nodejs的json工具格式化数据: npm i -g json

Cache使用

添加Cache

vim build.gradle
dependencies {
    compile("org.springframework.boot:spring-boot-starter-cache")
}

打开Cache

vim src/main/java/com/example/CacheDemo/DemoApplication.java
package com.example.CacheDemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class DemoApplication {

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

使用Cache

vim src/main/java/com/example/CacheDemo/ProductService.java
package com.example.CacheDemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
@CacheConfig(cacheNames = "products")
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    @Cacheable
    public Iterable<Product> findAll() {
        return productRepository.findAll();
    }

    @Cacheable
    public Optional<Product> findById(Integer id) {
        return productRepository.findById(id);
    }
}
  • 测试用例1

为了了解Cache过程 首先需要打开SQL调试如下

echo "spring.jpa.show-sql=true" >> src/main/resources/application.properties
./gradlew bootrun
# 第一次
curl localhost:8080/products | json
# Hibernate: select product0_.id as id1_0_, product0_.product_name as product_2_0_ from products product0_
[
  {
    "id": 1,
    "productName": "Product01"
  },
  {
    "id": 2,
    "productName": "Product02"
  }
]
# 第二次
curl localhost:8080/products | json
# 没有Hibernate SQL语句
[
  {
    "id": 1,
    "productName": "Product01"
  },
  {
    "id": 2,
    "productName": "Product02"
  }
]
  • 测试用例2
# 第一次
curl localhost:8080/products/1 | json
# Hibernate: select product0_.id as id1_0_0_, product0_.product_name as product_2_0_0_ from products product0_ where product0_.id=?
{
  "id": 1,
  "productName": "Product01"
}
# 第二次
curl localhost:8080/products/1 | json
# 没有Hibernate SQL语句
{
  "id": 1,
  "productName": "Product01"
}
  • 测试用例3
# 第一次
curl localhost:8080/products/2 | json
# Hibernate: select product0_.id as id1_0_0_, product0_.product_name as product_2_0_0_ from products product0_ where product0_.id=?
{
  "id": 2,
  "productName": "Product02"
}
# 第二次
curl localhost:8080/products/2 | json
# 没有Hibernate SQL语句
{
  "id": 2,
  "productName": "Product02"
}

Cache配置

配置Key

默认配置会按照函数的所有参数组合作为key值

vim src/main/java/com/example/CacheDemo/ProductService.java
package com.example.CacheDemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
@CacheConfig(cacheNames = "products")
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    @Cacheable(key = "'all'")
    public Iterable<Product> findAll() {
        return productRepository.findAll();
    }

    @Cacheable(key = "'one'")
    public Optional<Product> findById(Integer id) {
        return productRepository.findById(id);
    }
}
  • 测试用例1
./gradlew bootrun
# 第一次
curl localhost:8080/products | json
# Hibernate: select product0_.id as id1_0_, product0_.product_name as product_2_0_ from products product0_
[
  {
    "id": 1,
    "productName": "Product01"
  },
  {
    "id": 2,
    "productName": "Product02"
  }
]
# 第二次
curl localhost:8080/products | json
# 没有Hibernate SQL语句
[
  {
    "id": 1,
    "productName": "Product01"
  },
  {
    "id": 2,
    "productName": "Product02"
  }
]
  • 测试用例2
# 第一次
curl localhost:8080/products/1 | json
# Hibernate: select product0_.id as id1_0_0_, product0_.product_name as product_2_0_0_ from products product0_ where product0_.id=?
{
  "id": 1,
  "productName": "Product01"
}
# 第二次
curl localhost:8080/products/1 | json
# 没有Hibernate SQL语句
{
  "id": 1,
  "productName": "Product01"
}
  • 测试用例3
# 第一次
curl localhost:8080/products/2 | json
# 没有Hibernate SQL语句
{
  "id": 1,
  "productName": "Product01"
}
# 第二次
curl localhost:8080/products/2 | json
# 没有Hibernate SQL语句
{
  "id": 1,
  "productName": "Product01"
}
sed -i "" "s/'one'/#p0/g" src/main/java/com/example/CacheDemo/ProductService.java
  • 测试用例1
./gradlew bootrun
# 第一次
curl localhost:8080/products | json
# Hibernate: select product0_.id as id1_0_, product0_.product_name as product_2_0_ from products product0_
[
  {
    "id": 1,
    "productName": "Product01"
  },
  {
    "id": 2,
    "productName": "Product02"
  }
]
# 第二次
curl localhost:8080/products | json
# 没有Hibernate SQL语句
[
  {
    "id": 1,
    "productName": "Product01"
  },
  {
    "id": 2,
    "productName": "Product02"
  }
]
  • 测试用例2
# 第一次
curl localhost:8080/products/1 | json
# Hibernate: select product0_.id as id1_0_0_, product0_.product_name as product_2_0_0_ from products product0_ where product0_.id=?
{
  "id": 1,
  "productName": "Product01"
}
# 第二次
curl localhost:8080/products/1 | json
# 没有Hibernate SQL语句
{
  "id": 1,
  "productName": "Product01"
}
  • 测试用例3
# 第一次
curl localhost:8080/products/2 | json
# Hibernate: select product0_.id as id1_0_0_, product0_.product_name as product_2_0_0_ from products product0_ where product0_.id=?
{
  "id": 2,
  "productName": "Product02"
}
# 第二次
curl localhost:8080/products/2 | json
# 没有Hibernate SQL语句
{
  "id": 2,
  "productName": "Product02"
}

配置Ehcache

vim build.gradle
dependencies {
    compile('org.ehcache:ehcache:3.4.0')
    compile('javax.cache:cache-api')
}
vim src/main/resources/ehcache.xml
<ehcache:config
        xmlns:ehcache="http://www.ehcache.org/v3">
    <ehcache:cache alias="products">
        <ehcache:heap unit="entries">2000</ehcache:heap>
    </ehcache:cache>
</ehcache:config>

更多配置以及说明 可以参考Examples

echo "spring.cache.jcache.config=classpath:ehcache.xml" >> src/main/resources/application.properties
  • 测试
./gradlew bootrun
[           main] org.ehcache.xml.XmlConfiguration         : Loading Ehcache XML configuration from ***/CacheDemo/build/resources/main/ehcache.xml.
[           main] org.ehcache.core.EhcacheManager          : Cache 'products' created in Eh107InternalCacheManager.
[           main] org.ehcache.jsr107.Eh107CacheManager     : Registering Ehcache MBean javax.cache:type=CacheStatistics,CacheManager=file.***/CacheDemo/build/resources/main/ehcache.xml,Cache=products
[           main] org.ehcache.jsr107.Eh107CacheManager     : Registering Ehcache MBean javax.cache:type=CacheStatistics,CacheManager=file.***/CacheDemo/build/resources/main/ehcache.xml,Cache=products

配置Expiry

sed -i "" "s/#p0/'one'/g" src/main/java/com/example/CacheDemo/ProductService.java
vim src/main/resources/ehcache.xml
<ehcache:config
        xmlns:ehcache="http://www.ehcache.org/v3">
    <ehcache:cache alias="products">
        <ehcache:expiry>
            <ehcache:ttl unit="seconds">10</ehcache:ttl>
        </ehcache:expiry>
        <ehcache:heap unit="entries">2000</ehcache:heap>
    </ehcache:cache>
</ehcache:config>
  • 测试
./gradlew bootrun
# 第一次
curl localhost:8080/products/1 | json
# Hibernate: select product0_.id as id1_0_0_, product0_.product_name as product_2_0_0_ from products product0_ where product0_.id=?
{
  "id": 1,
  "productName": "Product01"
}
# 第二次
curl localhost:8080/products/2 | json
# 没有Hibernate SQL语句
{
  "id": 1,
  "productName": "Product01"
}
# 第三次 (第一次请求10秒钟后)
curl localhost:8080/products/2 | json
# Hibernate: select product0_.id as id1_0_0_, product0_.product_name as product_2_0_0_ from products product0_ where product0_.id=?
{
  "id": 2,
  "productName": "Product02"
}

Cache清除

vim src/main/java/com/example/CacheDemo/CacheController.java
package com.example.CacheDemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CacheController {

    @Autowired
    private CacheManager cacheManager;

    @GetMapping(path = "/clear-cache")
    public String clearCache() {
        Iterable<String> cacheNames = cacheManager.getCacheNames();
        System.out.println(cacheNames.toString());
        for (String cacheName : cacheNames) {
            cacheManager.getCache(cacheName).clear();
        }
        return "clear-cache";
    }
}
  • 测试
./gradlew bootrun
curl localhost:8080/products/1 | json
{
  "id": 1,
  "productName": "Product01"
}
curl localhost:8080/clear-cache
# 打印"[products]" & 返回"clear-cache"
curl localhost:8080/products/2 | json
{
  "id": 2,
  "productName": "Product02"
}

再谈Cache

Cache原理

高速存储代替慢速存储

小结果集快速查询代替大结果集普通查询

Cache缺点

高数据一致性导致低性能

高性能导致低数据一致性

Cache场景

更新不频繁的I/O密集型数据

增强系统可靠性 但同样需要预防缓存穿透和缓存雪崩的问题

关于更多"缓存穿透和缓存雪崩" 可以参考本文参考附录

参考

相关文章

网友评论

    本文标题:Spring Boot进阶 之 Cache

    本文链接:https://www.haomeiwen.com/subject/tedrdftx.html