本文的示例代码参考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密集型数据
增强系统可靠性 但同样需要预防缓存穿透和缓存雪崩的问题
关于更多"缓存穿透和缓存雪崩" 可以参考本文参考附录
网友评论