缓存入门
导入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
开启缓存
java
@EnableCaching
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
常用注解
常用注解:
@CacheConfig
、@Cacheable
、@CacheEvict
、@CachePut
、@Caching
。
@CacheConfig
: 常用于类级别的注解,用于统一配置类缓存的公共属性。@Caching
: 可以在一个类或方法同时使用多个缓存相关注解,灵活配置缓存策略。@Cacheable
: 用于标记一个类或方法,当被标记对象被访问,会先去缓存中查询相应的结果,如果存在则返回,不存在则指向方法。@CacheEvict
: 用于从缓存中移除数据。当使用@CacheEvict
注解标记一个方法时,该方法执行后会触发缓存的清除操作。@CachePut
: 用于将方法的返回值存储到缓存中。与@Cacheable
注解不同的是,@CachePut
注解每次都会触发方法的执行,并将结果存储到缓存中。
缓存示例
java
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import top.haijunit.work.core.doamin.PageQuery;
import top.haijunit.work.core.doamin.ResultPage;
import java.util.Collection;
import java.util.List;
/**
* `@CacheConfig` 标记类 全局配,置避免重复配置`cacheNames`、`keyGenerator`等属性
* `Cacheable` 标记方法,当被访问时,会先去缓存中查询相应的结果,如果存在则返回,不存在则指向方法
* `CacheEvict` 标记方法,删除对应的缓存中的数据
* `CachePut` 标记方法,方法执行后会触发缓存的更新操作
*/
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
@CacheConfig(cacheNames = {"cache:test"})
public class CacheTestService {
private final List<String> dataList = CollUtil.newArrayList("a", "b", "c");
/**
* 查询分页列表。
* 当查询条件为空或参数长度为0时,缓存查询结果。
*/
@Cacheable(key = "#pageQuery.getPage() + '_' + #pageQuery.getSize()", condition = "#info == null || #info.isEmpty()")
public ResultPage<String> selectPageList(PageQuery pageQuery, String info) {
log.info("分页查询,条件:{}, 分页: {}", info, pageQuery);
Collection<String> list = selectList(info).stream().skip(pageQuery.offset()).limit(pageQuery.getSize()).toList();
return ResultPage.of(list, pageQuery.getPage(), pageQuery.getSize(), list.size());
}
/**
* 查询所有或根据参数过滤的数据列表。
* 使用@Cacheable注解来缓存查询结果。
*/
@Cacheable(key = "'list'", condition = "#dto ==null || #dto.length() == 0")
public Collection<String> selectList(String dto) {
log.info("查询列表,条件:{}", dto);
if (CharSequenceUtil.isBlank(dto)) {
return dataList;
}
return dataList.stream().filter(item -> CharSequenceUtil.containsAnyIgnoreCase(item, dto)).toList();
}
/**
* 根据code获取详细信息。
* 使用@Cacheable注解来缓存单个对象的详细信息。
*/
@Cacheable(key = "#code", unless = "#result == null")
public String selectDetail(String code) {
log.info("查询详情,code:{}", code);
if (CharSequenceUtil.isBlank(code)) {
return null;
}
return dataList.stream().filter(item -> CharSequenceUtil.containsAnyIgnoreCase(item, code)).findFirst().orElse(null);
}
/**
* 插入新数据。
* 使用@CachePut注解来更新缓存中的值。
*/
@CacheEvict(allEntries = true)
public String insert(String code) {
log.info("插入数据,code:{}", code);
if (CharSequenceUtil.isBlank(code)) {
return null;
}
if (dataList.contains(code)) {
log.warn("插入失败,数据已存在: {}", code);
return null;
}
dataList.add(code);
return code;
}
/**
* 删除数据。
* 使用@CacheEvict注解来从缓存中移除项目。
*/
@CacheEvict(allEntries = true)
public String delete(String code) {
log.info("删除数据,code:{}", code);
if (CharSequenceUtil.isBlank(code)) {
return null;
}
if (!dataList.contains(code)) {
log.warn("删除失败,数据不存在: {}", code);
return null;
}
dataList.remove(code);
return code;
}
}
缓存进阶
目标
- 1、实现两种CacheManager,本地(Caffeine)+ 远程(Redisson)。
- 2、可以通过配置
spring.cache.type
来切换本地缓存实现,可以关闭全部缓存实现。 - 3、一级缓存(本地缓存)+ 二级缓存(Redis),通过多级缓存,提高缓存的利用率。(非必须,没有想到对应的业务场景)
- 3、本地缓存和远程缓存的同步,实现数据一致性。(非必须,没有想到对应的业务场景)
方案实现
- 配置
spring.cache.type
,默认使用本地缓存。
本地缓存Caffeine
POM依赖
xml
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
应用实践
java
@Slf4j
class CaffeineCacheTest {
Cache<Object, Object> cache;
@TestBefore
void init() {
cache = Caffeine.newBuilder()
//初始数量
.initialCapacity(10)
//最大条数
.maximumSize(10)
//expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准
//最后一次写操作后经过指定时间过期
.expireAfterWrite(1, TimeUnit.SECONDS)
//最后一次读或写操作后经过指定时间过期
.expireAfterAccess(1, TimeUnit.SECONDS)
//监听缓存被移除
.removalListener((key, val, removalCause) -> { })
//记录命中
.recordStats()
.build();
}
@Test
void test() {
cache.put("1", "张三");
// 取值
log.info("{}", cache.getIfPresent("1"));
// 默认值
log.info("{}", cache.get("2", o -> "默认值"));
}
}
SpringBoot中运用
java
@Component
public class AgentCache {
/**
* 直播间代理员基本信息(批量)
*/
private LoadingCache<Long, List<Agent>> agentBaseInfoCache = null;
/**
* 直播间代理员信息(单个)
*/
private LoadingCache<Long, AgentModel> agentModelCache = null;
@Resource
private IZbSiteAttributeService siteAttributeService;
@Autowired
private IAgentService agentService;
@PostConstruct
private void init() {
agentBaseInfoCache = Caffeine.newBuilder()
//设置本地缓存容器的初始容量
.initialCapacity(10)
//设置本地缓存的最大容量
.maximumSize(1000)
//设置缓存后多少秒过期
.expireAfterWrite(3600, TimeUnit.SECONDS).build(new CacheLoader<Long, List<Agent>>() {
@Override
public @Nullable List<Agent> load(Long zbId) {
return agentService.lambdaQuery()
.select(Agent::getUserId, Agent::getId, Agent::getParentCode, Agent::getLevel, Agent::getWeComAccount, Agent::getState, Agent::getStoreId)
.eq(Agent::getZbId, zbId)
.in(Agent::getState, HealthAgentConstants.AgentState.NORMA, HealthAgentConstants.AgentState.DISABLE)
.list();
}
});
agentModelCache = Caffeine.newBuilder()
//设置本地缓存容器的初始容量
.initialCapacity(10)
//设置本地缓存的最大容量
.maximumSize(1000)
//设置缓存后多少秒过期
.expireAfterWrite(3600, TimeUnit.SECONDS).build(new CacheLoader<Long, AgentModel>() {
@Override
public AgentModel load(Long zbId) {
String attributeVal = siteAttributeService.getAttributeVal(zbId, SiteAttributeEnum.AGENT_SETTING_MODEL.getStatus());
return StringUtils.isNotBlank(attributeVal) ? JsonUtil.fromJson(attributeVal, AgentModel.class) : new AgentModel();
}
});
}
/**
* 获取正常代理的用户ID
*/
public List<Long> get(Long zbId) {
return agentBaseInfoCache.get(zbId)
.stream()
.filter(a -> HealthAgentConstants.AgentState.NORMA == a.getState())
.map(Agent::getUserId)
.collect(Collectors.toList());
}
/**
* 获取所有代理员用户ID 包含了正常 & 禁用
*/
public List<Long> getAllAgentUserId(Long zbId) {
return agentBaseInfoCache.get(zbId)
.stream()
.map(Agent::getUserId)
.collect(Collectors.toList());
}
/**
* 获取正常代理的用户代理集合
*/
public List<Agent> getAgentBaseInfoList(Long zbId) {
return agentBaseInfoCache.get(zbId)
.stream()
.filter(a -> HealthAgentConstants.AgentState.NORMA == a.getState())
.collect(Collectors.toList());
}
/**
* 获取代理的用户代理集合-所有
*/
public List<Agent> getAllAgentBaseInfoList(Long zbId) {
return agentBaseInfoCache.get(zbId).stream().collect(Collectors.toList());
}
public Agent getAgentBaseInfo(Long zbId, Long userId) {
return agentBaseInfoCache.get(zbId).stream().filter(w -> w.getUserId().equals(userId)).findFirst().orElse(null);
}
public void invalidate(Long zbId) {
agentBaseInfoCache.invalidate(zbId);
// agentBaseInfoCache.refresh(zbId);
}
/**
* 获取AgentModel缓存
*/
public AgentModel getAgentModel(Long zbId) {
AgentModel model;
try {
model = agentModelCache.get(zbId);
} catch (Exception e) {
String attributeVal = siteAttributeService.getAttributeVal(zbId, SiteAttributeEnum.AGENT_SETTING_MODEL.getStatus());
return StringUtils.isNotBlank(attributeVal) ? JsonUtil.fromJson(attributeVal, AgentModel.class) : new AgentModel();
}
if (model == null) {
model = new AgentModel();
}
return model;
}
/**清除AgentModel本地缓存*/
public void refreshAgentModel(Long key) {
try {
agentModelCache.refresh(key);
} catch (Exception ignored) {
}
}
}