一、回忆一下该项目之前所使用的数据结构
1、 有用到redis缓存技术的是访问商品的详情页面前先访问redis(kv结构)。
2、 有用到hashMap结构的是商品的详情页面制作spu下其他sku商品为哈希表(hash结构)。
二、这里购物车的缓存使用hashmap数据结构
1、 存储的是购物车集合
2、 键:用户id
3、 购物车里的某个sku商品的更新
4、 使用哪个数据结构较好?kv和hashmap
×如果使用kv结构,取出json,转换为集合,集合再遍历出对象,修改对象,再放回集合,转为json,最后放回缓存。(较麻烦)
√使用hash结构存储(方便修改和查询购物车某个对象数据)
代码实现(注意这里有一个重大bug,同步缓存hmset进去之前,要先删除之前的缓存!!!!):
//同步到Redis缓存中
@Override
public void flushCartCache(String memberId) {
OmsCartItem omsCartItem = new OmsCartItem();
omsCartItem.setMemberId(memberId);
List<OmsCartItem> omsCartItemList = omsCartItemMapper.select(omsCartItem); //查询某个用户memberId的购物车
Jedis jedis = redisUtil.getJedis();
HashMap<String,String> hashMap = new HashMap<>();
for (OmsCartItem cartItem : omsCartItemList) { //遍历该购物车列表
//在购物车当中用户点击商品的选中状态,需要查询缓存,但是缓存当中的总价格没有计算,这里在同步之前,就计算总价格!!!bug!!!
cartItem.setTotalPrice(cartItem.getPrice().multiply(cartItem.getQuantity()));
hashMap.put(cartItem.getProductSkuId(), JSON.toJSONString(cartItem)); //把skuId作为k,购物车对象作为v
}
jedis.del("user:"+memberId+":cart"); //重大的bug!!!要先删除原本的缓存,再同步新的缓存!!!
jedis.hmset("user:"+memberId+":cart",hashMap);
jedis.close();
}
5、这里的实体类出现了bug,商品的数量如果使用int类型,会导致tk通用mapping封装不上这个字段,无法添加到数据库。
所以这里的实体类要把int类型改成BigDecimal
web层还是int类型,需要new BigDecimal()对象接收quantity才行
6、启动类拉到gmall包下,否则会扫描不到redisUtil工具,不要忘了加注解扫描mapper包
@MapperScan(basePackages = "com.atguigu.gmall.cart.service.mapper")
7、redis使用哈希map数据结构的所有方法
redis-cli -h 172.25.0.11 -p 6379 //连接redis
keys * //查询所有key
hvals user:1:cart 查询(所有)用户id为1的所有购物车内容
hget user:1:cart 127 查询(一个)用户id为1的购物车的(一个)sku商品id为127的商品
hset user:1:cart 127 value 添加(一个)skuId为127及其其他所有(多个)value值到用户id为1的用户
del user:1:cart 删除用户id为1的所有购物车信息
三、既然有同步缓存,那当然还有查询缓存,在service服务层,类似之前做的商品详细页面添加的缓存技术一样,都是为了保护数据库的!代码如下:
//用户已经登录,查询该用户的db的购物车,此操作为用户最为频繁的操作,需要添加中间件缓存服务器
@Override
public List<OmsCartItem> cartList(String memberId) {
Jedis jedis = null;
List<OmsCartItem> omsCartItemList = new ArrayList<>();
try{
jedis = redisUtil.getJedis();
String member = "user:"+memberId+":cart";
List<String> hvals = jedis.hvals(member);
if(hvals.size()!=0){ //查询缓存,不为空,即存储了该用户的数据
for (String hval : hvals) {
OmsCartItem omsCartItem = JSON.parseObject(hval, OmsCartItem.class);
omsCartItemList.add(omsCartItem);
}
}else {
String token = UUID.randomUUID().toString();
String lock = jedis.set(member, token, "nx", "px", 10 * 1000); //防止缓存击穿
if(StringUtils.isNotBlank(lock) && lock.equals("OK")){
//操作数据库,需要被保护
OmsCartItem omsCartItem = new OmsCartItem();
omsCartItem.setMemberId(memberId);
omsCartItemList = omsCartItemMapper.select(omsCartItem);
HashMap<String,String> hashMap = new HashMap<>();
if(omsCartItemList.size()==0){ //查询数据库获得的是空值
jedis.setex(member,2*60,JSON.toJSONString("")); //防止缓存穿透
}else { //数据库中有值,同步到缓存当中
for (OmsCartItem cartItem : omsCartItemList) { //遍历该购物车列表
hashMap.put(cartItem.getProductSkuId(), JSON.toJSONString(cartItem)); //把skuId作为k,购物车对象作为v
}
jedis.hmset(member,hashMap);
}
String script ="if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, Collections.singletonList(member),Collections.singletonList(token)); //使用脚本删除锁,确保原子性执行,避免删除别人的锁
}else {
return cartList(memberId);
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
jedis.close();
}
return omsCartItemList;
}