商户查询缓存
什么是缓存
实际开发过程中数据量可以达到几千万,缓存可以作为避震器防止过高的数据访问猛冲系统,避免系统内的操作线程无法及时处理信息而瘫痪
缓存(Cache)
就是数据交换的缓冲区(储存临时数据的地方),我们俗称的"缓存"实际就是缓冲区内的数据(一般从数据库中获取存储于本地)
Static修饰
: 随着类的加载而被加载到内存之中从而作为本地缓存final修饰
: 其引用和对象之间的关系是固定的不能改变,因此不用担心赋值时导致缓存失效
java
// 本地用于高并发
Static final ConcurrentHashMap<K,V> map = new ConcurrentHashMap<>();
// 用于redis等缓存
static final Cache<K,V> USER_CACHE = CacheBuilder.newBuilder().build();
// 本地缓存
Static final Map<K,V> map = new HashMap();
缓存的优点和缺点
- 优点: 缓存数据存储于代码中而代码又运行在内存中(读写性能远高于磁盘),从而大大降低用户访问的并发量给服务器带来的读写压力
- 缺点: 需要维护原数据和缓存数据一致性,这就需要增加代码复杂度(维护起来比较麻烦)和运维成本(缓存需要搭建集群模式)
实际开发中,会构筑多级缓存来使系统运行速度进一步提升,如本地缓存与Redis中的缓存并发使用
浏览器缓存
: 主要是存在于浏览器端的缓存(css文件和js文件等静态资源)应用层缓存
: 可以分为tomcat本地缓存,比如使用map集合或者redis作为缓存数据库缓存
: 在数据库中有一片空间是buffer pool(缓存索引),增改查的数据都会先加载到mysql的缓存中CPU缓存
: 当代计算机最大的问题是cpu性能提升了,但内存读写速度没有跟上,所以为了适应当下的情况,增加了cpu的L1,L2,L3级的缓存
添加商户缓存
在Service层创建queryById方法
, 然后在ServiceImpl中重写该方法,在Controller中调用该方法
当我们使用浏览器发送请求访问一个商户信息时如果直接从数据库中查询商户信息效率很慢,所以一般在客户端与数据库之间加上一个Redis缓存
java
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
// 这里是直接调用Mybaits-Plus提供的方法查询数据库
return shopService.queryById(id);
}
重启服务器访问商户信息,观察控制台日志输出然后刷新页面, 控制台不会出现查询商户信息的SQL语句,在Redis图形化界面中也可以看到缓存的商户信息数据
java
// Redis中缓存店铺的key前缀
public static final String CACHE_SHOP_KEY = "cache:shop:";
java
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
// 先从Redis中查询店铺数据,如果缓存里有数据直接返回,如果缓存中没有则去查询数据库并存入Redis
return shopService.queryById(id);
}
public interface IShopService extends IService<Shop> {
Result queryById(Long id);
}
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public Result queryById(Long id) {
// 先从Redis中查询对应的店铺缓存信息,这里的key是店铺固定的前缀+查询的店铺id
String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
// 如果在Redis中查询到了店铺信息(String类型的JSON字符串)则转为Shop类型直接返回
if (StrUtil.isNotBlank(shopJson)) {
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
// 在Redis中没查询到店铺信息则根据店铺Id去数据库中查
Shop shop = getById(id);
// 在数据库中也查不到则返回一个错误信息或者返回空
if (shop == null){
return Result.fail("店铺不存在!!");
}
// 在数据库中查到了则把shop对象转为json字符串
String jsonStr = JSONUtil.toJsonStr(shop);
// 将转换后的json字符串存入Redis
stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonStr);
// 最终把查询到的店铺信息返回给前端
return Result.ok(shop);
}
}
添加商户类型数据缓存
当我们访问首页时,会自动发起shop-type/list
请求,查询不同商户类型的信息,由于这部分数据几乎是不变动的,所以我们可以将其一直存入Redis缓存
使用foreach循环
将查询到的多个店铺类型信息从JSON和Bean之间
相互转换,Redis中存储所有店铺类型的信息可以使用List
类型
java
data:[0: {id: 1, name: "美食", icon: "/types/ms.png", sort: 1}
1: {id: 2, name: "KTV", icon: "/types/KTV.png", sort: 2}
2: {id: 3, name: "丽人·美发", icon: "/types/lrmf.png", sort: 3}
3: {id: 10, name: "美睫·美甲", icon: "/types/mjmj.png", sort: 4}
4: {id: 5, name: "按摩·足疗", icon: "/types/amzl.png", sort: 5}
5: {id: 6, name: "美容SPA", icon: "/types/spa.png", sort: 6}
6: {id: 7, name: "亲子游乐", icon: "/types/qzyl.png", sort: 7}
7: {id: 8, name: "酒吧", icon: "/types/jiuba.png", sort: 8}
8: {id: 9, name: "轰趴馆", icon: "/types/hpg.png", sort: 9}
9: {id: 4, name: "健身运动", icon: "/types/jsyd.png", sort: 10}]
java
// Redis中缓存所有店铺类型信息的List集合的key
public static final String CACHE_SHOP_TYPE_KEY = "cache:shop:type";
java
@GetMapping("list")
public Result queryTypeList() {
return typeService.queryList();
}
public interface IShopTypeService extends IService<ShopType> {
Result queryList();
}
@Override
public Result queryList() {
// 先从Redis中查询缓存店铺类型信息的对应List集合中,这里的常量值Redis中缓存所有店铺类型信息的List集合的key
List<String> shopTypes = stringRedisTemplate.opsForList().range(CACHE_SHOP_TYPE_KEY, 0, -1);
// 如果List集合不为空即查询到了店铺类型信息,则将List集合中每个String类型JSON字符串转为ShopType类型直接返回
if (!shopTypes.isEmpty()) {
List<ShopType> tmp = new ArrayList<>();
for (String types : shopTypes) {
ShopType shopType = JSONUtil.toBean(types, ShopType.class);
tmp.add(shopType);
}
return Result.ok(tmp);
}
// 如果Redis中没有对应的List集合,则去数据库表中查询所有店铺类型的信息
List<ShopType> tmp = query().orderByAsc("sort").list();
if (tmp == null){
return Result.fail("店铺类型不存在!!");
}
// 将查到的每个shopType类型的对象转换为JSON字符串,然后存入Redis中缓存店铺类型信息的List集合中
for (ShopType shopType : tmp) {
String jsonStr = JSONUtil.toJsonStr(shopType);
shopTypes.add(jsonStr);
}
// 向Redis中存入查询到的List集合
stringRedisTemplate.opsForList().leftPushAll(CACHE_SHOP_TYPE_KEY,shopTypes);
// 最终把查询到的所有商户类型信息返回给前端
return Result.ok(tmp);
}
// 使用stream流简化代码
@Override
public Result queryList() {
// 先从Redis中查询缓存店铺类型信息的对应List集合,这里的常量值Redis中缓存所有店铺类型信息的List集合的key
List<String> shopTypes = stringRedisTemplate.opsForList().range(CACHE_SHOP_TYPE_KEY, 0, -1);
// 如果List集合不为空即查询到了店铺类型信息,则将List集合中每个String类型JSON字符串转为ShopType类型直接返回
if (!shopTypes.isEmpty()) {
List<ShopType> tmp = shopTypes.stream().map(type -> JSONUtil.toBean(type, ShopType.class))
.collect(Collectors.toList());
return Result.ok(tmp);
}
// 如果Redis中没有对应的List集合,则去数据库表中查询所有店铺类型的信息
List<ShopType> tmp = query().orderByAsc("sort").list();
if (tmp == null){
return Result.fail("店铺类型不存在!!");
}
// 将查到的每个shopType类型的对象转换为JSON字符串,然后存入Redis中缓存店铺类型信息的List集合中
shopTypes = tmp.stream().map(type -> JSONUtil.toJsonStr(type))
.collect(Collectors.toList());
// 向Redis中存入查询到的List集合
stringRedisTemplate.opsForList().leftPushAll(CACHE_SHOP_TYPE_KEY,shopTypes);
// 最终把查询到的所有商户类型信息返回给前端
return Result.ok(tmp);
}