1 摘要
JetCache 是阿里出品的一款缓存框架,其提供了基于接口的缓存以及能够实现本地和远程二级缓存(本地缓存是当前 JVM 中的LinkedHashMap,远程缓存为Redis),在提高系统吞吐量的同时,也极大地方便了代码编写。本文将介绍基于 Spring Boot 2.5 简易集成缓存框架 JetCAche 2.7。
JetCache Github: https://github.com/alibaba/jetcache
2 核心 Maven 依赖
./demo-mybatis-plus/pom.xml
<!-- jetcache 缓存框架 -->
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis-lettuce</artifactId>
<version>${jetcache.version}</version>
</dependency>
其中版本信息为:
<jetcache.version>2.7.3</jetcache.version>
本示例中使用的是 lettuce 作为 Redis 连接工具
3 核心代码
3.1 JetCache 配置
./demo-mybatis-plus/src/main/resources/application.yml
# jetCache
jetcache:
statIntervalMinutes: 1
areaInCacheName: false
local:
jetcache-demo:
type: linkedhashmap
keyConvertor: jackson
remote:
jetcache-demo:
type: redis.lettuce
keyConvertor: jackson
broadcastChannel: jetcache-demo
keyPrefix: jetcache-demo
valueEncoder: java
valueDecoder: java
uri: redis://your_password@127.0.0.1:6379
defaultExpireInMillis: 5000
poolConfig:
minIdle: 5
maxIdle: 20
maxTotal: 50
注意:
jetcache.local.jetcache-demo
: 这里的 jetcache-demo
是作为缓存区域(area
)命名的,必须与代码中的 area
定义保持一致,否则就会启动失败,抛出com.alicp.jetcache.CacheConfigException: no local cache builder
异常。
同理 jetcache.remote.jetcache-demo
中jetcache-demo
也是一样的。一般同一个项目(业务模块)使用同样的 area
命名。
官方配置信息说明:
https://github.com/alibaba/jetcache/blob/master/docs/CN/Config.md
3.2 常用注解
官方文档注解说明:
https://github.com/alibaba/jetcache/blob/master/docs/CN/MethodCache.md
一些常用注解的补充说明:
@Cached
:标记缓存注解,用于方法上,一般用于查询方法上常用参数包括:
注解参数 | 说明 |
---|---|
area | 区域,必须定义的与 application 里边的保持一致(local/remote 后一级的参数名),否则运行报错 |
name | 缓存的 key(前缀) |
key | 缓存的key后边部分,拼在name后边,可缺省,定义的key必须在请求参数中包括,否则报错,无法缓存 |
expire | 缓存失效时间,默认单位:秒 |
localExpire | 本地缓存失效时间,默认单位:秒 |
cacheType | 缓存类型,有本地,远程和两者都包含可选,简易使用CacheType.BOTH |
localLimit | 本地缓存数量限制 |
@CacheInvalidate
:缓存失效注解,用于方法上,一般用于更新或者删除方法上。
@CachePenetrationProtect
: 指定未命中缓存的情况下,同一个 JVM 同一个 key 只有一个线程去操作,用于防止并发请求。
3.3 注解使用示例
./demo-mybatis-plus/src/main/java/com/ljq/demo/springboot/mybatisplus/service/impl/JetCacheUserServiceImpl.java
package com.ljq.demo.springboot.mybatisplus.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.StrUtil;
import com.alicp.jetcache.anno.CacheInvalidate;
import com.alicp.jetcache.anno.CachePenetrationProtect;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ljq.demo.springboot.mybatisplus.common.constant.JetCacheConst;
import com.ljq.demo.springboot.mybatisplus.dao.UserDao;
import com.ljq.demo.springboot.mybatisplus.model.entity.UserEntity;
import com.ljq.demo.springboot.mybatisplus.model.param.user.*;
import com.ljq.demo.springboot.mybatisplus.service.IJetCacheUserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.Objects;
/**
* @Description: JetCache 缓存示例用户业务实现类
* @Author: junqiang.lu
* @Date: 2023/3/25
*/
@Service
public class JetCacheUserServiceImpl extends ServiceImpl<UserDao, UserEntity> implements IJetCacheUserService {
/**
* 保存(单条)
*
* @param userSaveParam
* @return
*/
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
@Override
public UserEntity save(UserSaveParam userSaveParam) {
// 请求参数获取
UserEntity userParam = new UserEntity();
BeanUtil.copyProperties(userSaveParam,userParam,CopyOptions.create().ignoreNullValue().ignoreError());
// 保存
String nowTime = String.valueOf(System.currentTimeMillis());
userParam.setUserInsertTime(nowTime);
userParam.setUserUpdateTime(nowTime);
super.save(userParam);
return userParam;
}
/**
* 查询详情(单条)
*
* @param userInfoParam
* @return
*/
@CachePenetrationProtect
@Cached(area = JetCacheConst.CACHE_AREA, name = ":getUserById", key = "#userInfoParam.id",
cacheType = CacheType.BOTH, expire = 300, localExpire = 180)
@Override
public UserEntity info(UserInfoParam userInfoParam) {
return super.getById(userInfoParam.getId());
}
/**
* 查询列表
*
* @param userListParam
* @return
*/
@Override
public IPage<UserEntity> list(UserListParam userListParam) {
LambdaQueryWrapper<UserEntity> userWrapper = Wrappers.lambdaQuery();
userWrapper.likeRight(StrUtil.isNotBlank(userListParam.getUserName()), UserEntity::getUserName,
userListParam.getUserName());
IPage<UserEntity> page = new Page<>(userListParam.getCurrentPage(),userListParam.getPageSize());
userWrapper.orderBy(true, Objects.isNull(userListParam.getAscFlag()) ?
false : userListParam.getAscFlag(), UserEntity::getId);
return super.page(page,userWrapper);
}
/**
* 更新(单条)
*
* @param userUpdateParam
* @return
*/
@CacheInvalidate(area = JetCacheConst.CACHE_AREA, name = ":getUserById", key = "#userUpdateParam.id")
@Override
public UserEntity update(UserUpdateParam userUpdateParam) {
LambdaQueryWrapper<UserEntity> userWrapper = new LambdaQueryWrapper<>();
userWrapper.eq(true, UserEntity::getId, userUpdateParam.getId());
int countUser = super.count(userWrapper);
if (countUser < 1) {
return null;
}
// 请求参数获取
UserEntity userParam = new UserEntity();
BeanUtil.copyProperties(userUpdateParam, userParam, CopyOptions.create().ignoreNullValue().ignoreError());
userParam.setUserUpdateTime(String.valueOf(System.currentTimeMillis()));
super.updateById(userParam);
return userParam;
}
/**
* 删除(单条)
*
* @param userDeleteParam
* @return
*/
@CacheInvalidate(area = JetCacheConst.CACHE_AREA, name = ":getUserById", key = "#userDeleteParam.id")
@Override
public boolean delete(UserDeleteParam userDeleteParam) {
boolean deleteFlag = super.removeById(userDeleteParam.getId());
if (!deleteFlag) {
return false;
}
return true;
}
}
注意事项:
这里对于同一业务模块的area
和 name
要保持一致。area
可以用一个常量类来保存,name
可以在前边添加:
这样在Redis数据库中可以分级显示。
4 使用效果
说明:
由于 JetCache 默认使用的是 Java提供的序列化,所以在 Redis 中看到的效果就是这样的,JetCache 2.7 版本官方不提供 Json 序列化方式,需要自行实现(后期再来更新)。
5 使用总结
- 1 JetCache 适用于标准的查询单条数据或者查询列表数据的接口进行完整的缓存,这就要求接口的入参必须包含唯一健,且返回值必须是DTO对象。同时对于分页查询的接口无法实现缓存,也没有意义。
- 2 JetCache 默认使用的是 JDK 提供的序列化,JDK序列化性能差,可读性差。在目前项目开发过程中,一般 Redis 缓存都使用 JSON 格式对 Value 进行序列化。
- 3 JetCache 最大的优势是实现了本地和远程的二级缓存,相较于使用 Redis 缓存,使用本地缓存能够提供更高的吞吐量。
6 推荐参考资料
7 Github 源码
Gtihub 源码地址 : https://github.com/Flying9001/springBootDemo
个人公众号:404Code,分享半个互联网人的技术与思考,感兴趣的可以关注.