Spring 对各种缓存技术抽象成了统一的接口和常用的操作方法,对不同的缓存技术,如 redis, ehcache 等透明地添加缓存的支持。
只要使用了缓存注解@EnableCaching
,Spring Boot就会自动配置缓存基本设置。
Spring 缓存支持
Spring 提供了 org.springframework.cache.CacheManager 接口来统一管理缓存技术,org.springframework.cache.Cache接口包含缓存常用的CRUD操作方法,但一般不会使用此接口。
CacheManager类型
Cache |
CacheManager |
描述 |
Generic |
通用缓存 |
如果定义了Cache Bean,则所有该Bean类型的 CacheManager都会被创建 |
JCache (JSR-107) |
JCacheCacheManager |
支持JSR-107标准的实现作为缓存技术,如 EhCache 3, Hazelcast, Infinispan等 |
EhCache 2.x |
EhCacheCacheManager |
使用EhCache 2.x作用存储缓存 |
Couchbase |
CouchbaseCacheManager |
使用分布式Couchbase作为存储缓存 |
Redis |
RedisCacheManager |
使用Redis作为存储缓存 |
Caffeine |
CaffeineCacheManager |
Caffeine是对Guava缓存的Java 8重写,取代了对Guava的支持。Caffeine 2.1 or higher |
Simple |
SimpleCacheManager |
使用给定的collection来作为存储缓存,常用于测试或作简单声明,如果没有提供缓存,则使用ConcurrentHashMap 来作为缓存的简单实现 |
CacheManager实现
使用 CacheManager 时,需要注册该实现该接口的 Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration @EnableCaching public class AppConfig {
@Bean public EhCacheCacheManager cacheManager(CacheManager ehCacheCacheManager){ return new EhCacheCacheManager(); } }
|
声名式缓存注解
Spring 提供了 4个注解来声明缓存规则(使用AOP实现)。
- @Cacheable
方法的结果可以被缓存。查询时会先从缓存中取,如果没有再执行方法返回数据并缓存。
- key 属性可以是 SpEL 表达式.
- keyGenerator 属性是指定生成键的 Bean 的名称。
- @CachePut
总是执行方法返回数据并缓存,不会跳过方法的执行。
- @CacheEvit
如果存在缓存数据,则清除所有。
- @Caching
可通过该注解来组合多个注解(上面三个注解),元注解作为该注解的属性来使用。
Spring Boot的支持
Spring Boot自动配置了多个 CacheManager 的实现,自动配置在 org.springframework.boot.autoconfigure.cache 包下。

缓存的属性类是 CacheProperties,以spring.cache
为前缀的属性来配置缓存。
Spring Boot环境下,使用缓存只需要导入相应的缓存依赖包,并在配置类上使用@EnableCaching
开启缓存支持。
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
|
备注:查看 spring-boot-starter-cache 的依赖,依赖了 spring-boot-starter, context, context-support包,这些包也被包含在在 Sping Boot其它组件里,没有独有的依赖包,所以如果项目已存在这三个依赖是可以不添加spring-boot-starter-cache也能使用默认缓存。如果要使用默认的 ConcurrentHashMap 来做缓存,就不能添加第三方缓存依赖(ehcache,redis),否则会启用第三方缓存技术。
Simple
示例依赖了JPA
,需要导入spring-boot-starter-data-jpa;使用 Log4j2
和fastJson
,需要导入依赖。
- 开启缓存支持
1 2 3 4 5 6 7 8
| import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Configuration;
@Configuration @EnableCaching public class CacheConfig {
}
|
- Category实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import com.fasterxml.jackson.annotation.JsonFormat;
import javax.persistence.Entity; import javax.persistence.Id; import java.util.Date;
@Entity public class Category {
@Id private Long CategoryId; private String name; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date lastUpdate;
}
|
- Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import com.alibaba.fastjson.JSON; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController @RequestMapping(value = "/cache/category") public class CategoryController { private final static Logger logger = LogManager.getLogger(CategoryController.class);
@Autowired private CategoryService categoryService;
@RequestMapping("/queryById") public Category queryById(Long CategoryId){ Category category = categoryService.queryById(CategoryId); logger.info(JSON.toJSONString(category)); return category; }
@RequestMapping("/queryByCategoryId") public Category queryByCategoryId(Category category){ category = categoryService.queryByCategoryId(category); logger.info(JSON.toJSONString(category)); return category; }
@RequestMapping("/saveCategory") public Category saveCategory(){ Category category = new Category().setCategoryId(18L).setLastUpdate(new Date()).setName("Hello Kitty"); category = categoryService.saveCategory(category); logger.info(JSON.toJSONString(category)); return category; }
@RequestMapping("/deleteById") public void deleteById(Long categoryId){ categoryService.deleteById(categoryId); }
@RequestMapping("/queryByName") public Category queryByName(Category category){ category = categoryService.queryByName(category); logger.info(JSON.toJSONString(category)); return category; }
@RequestMapping("/queryByCategoryName") public Category queryByCategoryName(Category category){ category = categoryService.queryByCategoryName(category); logger.info(JSON.toJSONString(category)); return category; }
}
|
- Service接口
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface CategoryService { Category queryById(Long categoryId);
Category queryByCategoryId(Category category);
Category queryByName(Category category);
Category queryByCategoryName(Category category);
Category saveCategory(Category category);
void deleteById(Long categoryId); }
|
- ServiceImpl接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| 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.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import org.springframework.stereotype.Service;
@Service public class CategoryServiceImpl implements CategoryService {
@Autowired private CategoryRepository categoryRepository;
@Override @Cacheable(key = "#categoryId", value = "category") public Category queryById(Long categoryId) { return categoryRepository.findById(categoryId).get(); }
@Override @Cacheable(key = "#category.CategoryId", value = "category") public Category queryByCategoryId(Category category) { return categoryRepository.findById(category.getCategoryId()).get(); }
@Override @Cacheable(key = "#category.CategoryId", value = "category") public Category saveCategory(Category category) { return categoryRepository.save(category); }
@Override @CacheEvict(value = "category") public void deleteById(Long categoryId) { categoryRepository.deleteById(categoryId); }
@Override @CachePut(key = "#category.name", value = "category") public Category queryByName(Category category) { Example<Category> example = new Example<Category>(){
@Override public Category getProbe() { return category; }
@Override public ExampleMatcher getMatcher() { ExampleMatcher matcher = ExampleMatcher.matchingAny(); return matcher; } };
return categoryRepository.findOne(example).get(); }
@Override @Cacheable(key = "#category.name", value = "category") public Category queryByCategoryName(Category category) { Example<Category> example = new Example<Category>(){
@Override public Category getProbe() { return category; }
@Override public ExampleMatcher getMatcher() { ExampleMatcher matcher = ExampleMatcher.matchingAny(); return matcher; } }; return categoryRepository.findOne(example).get(); } }
|
- CategoryRepository
1 2 3 4
| import org.springframework.data.jpa.repository.JpaRepository;
public interface CategoryRepository extends JpaRepository<Category, Long> { }
|
相关参考
- Spring Cache 抽象 官网
- Spring Boot Caching 官网
- Caffeine 缓存 官网
- Cache抽象详解
- Spring Boot 缓存使用