1. Spring 的Cache框架
??????整合 不是分別整合幾種緩存,而是同時使用多種緩存。根據項目中不同的緩存需求采用不同的緩存技術。
1.1 一次聊天
??????前些日子和朋友聊天時,他們項目中用到緩存,聊天中了解到,他們的緩存采用的是自己寫的一個叫ICache的接口。緩存有redis緩存 和 OCS緩存(并不知道他們這個是什么鬼)。
看了他們接口API,基本和 spring的cache 框架一樣。
其實他們也可以使用spring cache ,只需要編寫一個OCSCache和 OCSCacheManager就行了。
一般項目中用緩存都是單一使用的,像朋友這樣用好幾種的不太多。其實spring cache 是支持多種緩存混合使用。
下面就是怎么利用spring cache 實現多種緩存技術的混合使用的。
1.2 Cache 和 CacheManager
spring 通過 cache 和 cacheManager 整合 和 管理不同緩存技術,通過 對應的CacheManager 管理 Cache,
比如 redis 、guava、ehcache、Jcache、還有自定義的cache。
- org.springframework.cache.Cache
- org.springframework.cache.CacheManager
1.3 緩存注解
??????? Spring Cache 十分強大,在使用緩存時,對項目幾乎沒有侵入,使用時
只需要在需要使用的方法上面加上對應的注解,并且配合強大的SPEL,就可以很簡單的使用各種緩存啦。以后切換緩存也是很方便。注解怎么使用可以參考其他的關于這方便的介紹。
- @Cacheable 查詢緩存
- @CacheEvict 刪除緩存條目
- @CachePut 更新緩存條目
- @Caching
- @CacheConfig
- @EnableCaching 啟用Spring Cache
1.4 CacheMangager
???????CacheManager簡單描述就是用來存放Cache,Cache用于存放具體的key-value值。舉個栗子:一個班級管理員 可以根據名字找到對應的學生,那么cacheManager 也是如此,CacheManager 負責緩存的創建和管理。常見的有下面集中。
- RedisCacheManager 管理redis緩存
- GuavaCacheManager 谷歌的guava緩存
- EhchcheManager EhCache 管理
- CompositeCacheManager 混合的緩存管理(可以同時使用多種緩存)
2. SpringBoot整合多種緩存
???????
有時候在項目中會用到多種緩存同時使用的情況,就需要通過Spring提供的CompositeCacheManager來整合多種緩沖,通過緩存名字來指定使用的緩存。恕我語言匱乏,實在不知道該怎么說,只能貼代碼了。有耐心的看下代碼,就知道怎么做了。不懂的可以留言問我。
2.1 引入jar
<!-- spring-cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 谷歌的guava cache -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.3-jre</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- EhCache -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
2.2 配置文件
- application.properties:
# redis配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-active=8
# 指定ehcahce 配置文件路徑
spring.cache.ehcache.config=cache/ehcache.xml
spring.redis.database=0
- ehcache.xml EhCachede配置文件,其實不想用XML.
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!-- diskStore:為緩存路徑,ehcache分為內存和磁盤兩級,此屬性定義磁盤的緩存位置。
參數解釋如下: user.home – 用戶主目錄
user.dir – 用戶當前工作目錄
java.io.tmpdir – 默認臨時文件路徑 -->
<diskStore path="java.io.tmpdir/Tmp_EhCache" />
<!-- defaultCache:默認緩存策略,當ehcache找不到定義的緩存時,則使用這個緩存策略。只能定義一個。 -->
<!-- name:緩存名稱。
maxElementsInMemory:緩存最大數目
maxElementsOnDisk:硬盤最大緩存個數。
eternal:對象是否永久有效,一但設置了,timeout將不起作用。
overflowToDisk:是否保存到磁盤,當系統當機時
timeToIdleSeconds:設置對象在失效前的允許閑置時間(單位:秒)。僅當eternal=false對象不是永久有效時使 用,可選屬性,默認值是0,也就是可閑置時間無窮大。
timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)。最大時間介于創建時間和失效時間之間。僅 當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。
diskPersistent:是否緩存虛擬機重啟期數據Whether the disk store persists between restarts
of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該 有自己的一個緩沖區。
diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內 存。默認策略是LRU(最近最少使用)。
你可以設置為FIFO(先進先出)或是LFU(較少使用)。
clearOnFlush:內存數量最大時是否清除。
memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,默認策略)、FIFO(先進先出)、LFU(最少 訪問次數)。
FIFO,first in first out,這個是大家最熟的,先進先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面 所講,緩存的元素有一個hit屬性,hit值最小的將會被清出緩存。
LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地 方來緩存新的元素的時候,
那么現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。 -->
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" />
<cache name="EhcacheA" eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="0" memoryStoreEvictionPolicy="LFU" />
</ehcache>
- RedisConfig Redis配置 這個很簡潔
package com.example.demo.config;
import org.apache.log4j.Logger;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
/**
*redis 配置
*/
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
private static Logger logger = Logger.getLogger(RedisConfig.class);
@Bean
@ConfigurationProperties(prefix="spring.redis")
public JedisPoolConfig getRedisConfig(){
JedisPoolConfig config = new JedisPoolConfig();
return config;
}
@Bean
public JedisConnectionFactory getConnectionFactory(RedisProperties redisProperties){
JedisConnectionFactory factory = new JedisConnectionFactory();
JedisPoolConfig config = getRedisConfig();
factory.setDatabase(redisProperties.getDatabase());
factory.setHostName(redisProperties.getHost());
factory.setPassword(redisProperties.getPassword());
factory.setPort(redisProperties.getPort());
factory.setPoolConfig(config);
logger.info("JedisConnectionFactory bean init success.");
return factory;
}
@Bean
public StringRedisTemplate getRedisTemplate( RedisProperties redisProperties){
JedisConnectionFactory connectionFactory = getConnectionFactory(redisProperties);
StringRedisTemplate template = new StringRedisTemplate(connectionFactory);
//設置redis 序列化
template.setStringSerializer(new StringRedisSerializer());
return template;
}
}
- CacheConfig.java 這個緩存的配置
package com.example.demo.config;
import com.example.demo.constant.CacheConstant;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.cache.guava.GuavaCacheManager;
import org.springframework.cache.support.CompositeCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.cache.DefaultRedisCachePrefix;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author huxingnan
* @date 2018/4/11 14:05
*/
@Configuration // 自動配置
@EnableCaching //啟用Spring cache
public class CacheConfig {
// 注釋掉的 是我自己 分別配置幾種緩存的時候用的 spring ioc 中只能有一個 CacheManager 實列,如果 有多個會報錯。
//@Bean
// public CacheManager guavaCacheManager() {
// GuavaCacheManager cacheManager = new GuavaCacheManager();
// cacheManager.setCacheBuilder(CacheBuilder.newBuilder().expireAfterWrite(3600, TimeUnit.SECONDS).maximumSize(1000));
// return cacheManager;
// }
//
// @Bean
// public CacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
// RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
// return redisCacheManager;
// }
@Value("${spring.cache.ehcache.config}")
private String ehCacheCongifPath;
/**
* @return
*/
@Bean
public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
System.out.println(ehCacheCongifPath);
cacheManagerFactoryBean.setConfigLocation(new ClassPathResource(ehCacheCongifPath));
cacheManagerFactoryBean.setShared(true);
//如果 Factory 自己手動實列化,需要 執行afterPropertiesSet()方法,因為這是方法是 初始化 類使用的
//如果Factory 由Spring 容器 創建 ,容器初始化完成后 spring 會去執行這個方法。
// cacheManagerFactoryBean.afterPropertiesSet();//初始化 讀取配置文件,
return cacheManagerFactoryBean;
}
/**
* 混合緩存管理
*
* @param redisTemplate redis template
* @return cacheManager
*/
@Bean
public CacheManager compositeCacheManager(@Autowired RedisTemplate<Object, Object> redisTemplate, @Autowired EhCacheManagerFactoryBean factoryBean) {
RedisCacheManager redisCacheManager = getRedisCacheManager(redisTemplate);
GuavaCacheManager guavaCacheManager = getGuavaCacheManager();
EhCacheCacheManager ehCacheCacheManager = ehCacheCacheManager(factoryBean);
CompositeCacheManager cacheManager = new CompositeCacheManager(redisCacheManager, guavaCacheManager, ehCacheCacheManager);
cacheManager.setFallbackToNoOpCache(true);
cacheManager.afterPropertiesSet();
return cacheManager;
}
/**
* 獲取guava 實列的緩存
*
* @return guava緩存管理 實列
*/
private GuavaCacheManager getGuavaCacheManager() {
GuavaCacheManager guavaCacheManager = new GuavaCacheManager();
guavaCacheManager.setCacheBuilder(CacheBuilder.newBuilder().expireAfterWrite(3600, TimeUnit.SECONDS).maximumSize(1000));
ArrayList<String> guavaCacheNames = Lists.newArrayList();
guavaCacheNames.add(CacheConstant.GUAVA_CACHE_A);
guavaCacheManager.setCacheNames(guavaCacheNames);
return guavaCacheManager;
}
/**
* 獲取redisCacheManager
*
* @param redisTemplate redisTemplate
* @return redisCacheManager
*/
private RedisCacheManager getRedisCacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
List<String> redisCacheNames = Lists.newArrayList();
redisCacheNames.add(CacheConstant.REDIS_CACHE_A);//一個cacheName 對應一個 緩存實列
redisCacheNames.add(CacheConstant.REDIS_CACHE_B);
redisCacheManager.setCacheNames(redisCacheNames);
//redis key 前綴
redisCacheManager.setCachePrefix(new DefaultRedisCachePrefix("demo"));//緩存key 前綴
redisCacheManager.setUsePrefix(true);//使用前綴
redisCacheManager.initializeCaches();//rediscache 需要初始化 緩存
return redisCacheManager;
}
/**
* EhCacheManager
*
* @return EhCacheManager
*/
private EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean factoryBean) {
EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager(factoryBean.getObject());
//由于自己實列化EhCacheManager 需要執行 手動初始化 方法。
ehCacheCacheManager.initializeCaches();//初始化
return ehCacheCacheManager;
}
}
2.3 怎么用
- 測試對象Account
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
private String id;
private String userName;
private String passWord;
@DateTimeFormat(pattern = "yyy-MM-dd")
private Date createTime;
private String alias;
private Integer level;
private Boolean vip;
}
- AccountService
/**
* @author huxingnan
* @date 2018/4/12 13:17
*/
@Service
public class AccountServiceImpl implements AccountService {
@Override
@CachePut(value = CacheConstant.EHCACHE_A,key = "#account.id")
public Account saveAccount(Account account) {
System.out.println("保存成功"+account);
account.setId("999");
account.setCreateTime(new Date());
return account;
}
@Override
@Cacheable(value = CacheConstant.EHCACHE_A,key = "#account.id")
public Account getAccountById(Account account) {
Account account1 = new Account(account.getId(),"zhangfei","zf12345",new Date(),"張飛",2,false);
return account1;
}
@Override
@Cacheable(value = CacheConstant.EHCACHE_A,key="#account.userName")
public List<Account> getAccountList(Account account) {
List<Account> accountList = Lists.newArrayList();
accountList.add(new Account("124","zhaoyun","zy9999",new Date(),"趙云",3,true));
accountList.add(new Account("125","lvbu","zy9999",new Date(),"呂布",3,true));
System.out.println("查詢accountList"+account);
return accountList;
}
@Override
@CacheEvict(value = CacheConstant.EHCACHE_A,key="#account.id")
public int deleteAccountById(Account account) {
System.out.println("刪除account"+account);
return 1;
}
@Override
@CacheEvict(value = CacheConstant.EHCACHE_A,key = "#p0.id")
public int updateAccountById(Account account) {
System.out.println("更新account"+account);
return 1;
}
}