美文网首页我爱编程
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