商城笔记-----
sale_attr_value_id不为null的话is_check就是1查出所有销售属性名和值并且用ischeck标记出当前skuselect ssa.*, ssav.id vid, ssav.sale_attr_value_name, IF(skuv.sale_attr_value_id IS NULL, "0", "1") is_checked from spu_sale_attr ssa left join spu_sale_attr_value ssav on ssa.spu_id = ssav.spu_id and ssa.base_sale_attr_id = ssav.base_sale_attr_id left join sku_sale_attr_value skuv on skuv.sku_id = #{skuId} and skuv.sale_attr_value_id = ssav.id where ssa.spu_id = #{spuId} order by ssa.base_sale_attr_id, ssav.id这块两个查询:查询所有销售属性并且用ischeck表示当前sku是哪种销售属性(ischeck表示)。查询所有销售属性的组合,并且表示是哪种sku。(sale_attr_value_id|sale_attr_value_id:skuid)把这两个数据给前端,前端就能确定当前sku的销售属性是哪个组合,并且切换销售属性,前端就能知道往后端发送请求时候携带的skuid了。用户切换一次销售属性,就往后端发送一次请求,并且携带相应的skuidselect resultType="com.atguigu.gmall.product.dto.ValueSkuJsonDTO" select a.sku_id, GROUP_CONCAT(DISTINCT a.sale_attr_value_id ORDER BY a.sale_attr_value_id ASC SEPARATOR '|') attr_value_concat from (select skuv.sku_id, skuv.sale_attr_value_id from sku_sale_attr_value skuv left join spu_sale_attr_value ssav on skuv.sale_attr_value_id = ssav.id where skuv.spu_id = #{spuId} ORDER BY skuv.sku_id, ssav.base_sale_attr_id, ssav.id) a GROUP BY a.sku_id /select嵌套三级分类@Data public class CategoryVo { private Long categoryId; //当前分类id private String categoryName; //当前分类名 private ListCategoryVo categoryChild; //子分类 } resultMap type="com.atguigu.gmall.web.CategoryVo" id property="categoryId" column="c1id"/id result property="categoryName" column="c1name"/result !-- 一级分类 -- collection property="categoryChild" ofType="com.atguigu.gmall.web.CategoryVo" id property="categoryId" column="c2id"/id result property="categoryName" column="c2name"/result !-- 二级分类 -- collection property="categoryChild" ofType="com.atguigu.gmall.web.CategoryVo" id property="categoryId" column="c3id"/id result property="categoryName" column="c3name"/result !-- 三级分类 -- /collection /collection /resultMap select resultMap="CategoryTreeRM" select bc1.id c1id, bc1.name c1name, bc2.id c2id, bc2.name c2name, bc3.id c3id, bc3.name c3name from base_category1 bc1 left join base_category2 bc2 on bc2.category1_id = bc1.id left join base_category3 bc3 on bc3.category2_id = bc2.id /select1、查询三级分类加了map缓存,吞吐量由300/s到8500/s。2、缓存没有查询数据库==回源一系列解决方案1缓存数据,有bugtry{ //1、查询redis String json = redisTemplate.opsForValue().get(key); if(StringUtils.isEmpty(json)){ return null; }else { //解决穿透,设置null值,不过我们设置的是x字符串。或者不用设置x,直接用布隆过滤器,把这个判断删除就行 if("x".equals(jsonStr)){ return null; } //3、缓存中有。直接返回 return json ; } //解决穿透 //布隆 RBloomFilterObject filter = redissonClient.getBloomFilter(bloomKey); //判断布隆有没有 if (!filter.contains("xxx")) { return null; } //解决击穿 //锁名一定不要为固定值,一旦为固定值查49,50商品为同一把锁。 RLock lock = redissonClient.getLock("lock-" + skuId); //阻塞式加锁(无限等待,设置释放时间就不会无限等待了), // 一定要等到锁。会进行续期,默认30秒,每隔10秒续到30秒。 // 参数:释放时间,单位。 // lock.lock(10, TimeUnit.SECONDS); //开启锁功能.尝试加锁。返回值是布尔类型。三个参数:等待时间(最多等多久),释放时间,单位 boolean b = lock.tryLock(); if (b) { //得到锁的逻辑 //8、加锁成功。回源。双检查机制:回源之前再查询一次缓存。(此代码没查缓存)原因: //两个线程同时发起抢锁命令,一个卡住了,别人释放后,另一个拿到锁 //10、目标方法执行。如果自己抓异常一定要往出抛,否则多切面情况下有可能引起切面逻辑失效 result = pjp.proceed(args);//回源代码 //11、放入缓存,缓存中的每个数据都应该有过期时间;临时数据缓存短一点时间 String jsonData = data==null? "x":Jsons.toStr(data); //缓存中的每个数据都应该有过期时间;临时数据缓存短一点时间 if(data == null){ redisTemplate.opsForValue().set(key,jsonData,30,TimeUnit.MINUTES);//存x,时间短一点 }else { redisTemplate.opsForValue().set(key,jsonData,ttl,timeUnit);//存商品详情,时间长一点 } //12、解锁放到finally return result; }else { //没得到锁的逻辑 //9、加锁失败。睡500毫秒再次查询缓存· Thread.sleep(500); return cacheOpsService.getCacheData(cacheKey, new TypeReferenceObject() { @Override public Type getType() { return methodReturnType; } }); } }finally { //redisson自动判断是不是自己的锁 //解锁一定要放到finally,不然会出现幽灵续期 lock.unlock(); } ----------------------------- 延迟双删 //这个池子的队列是Integer.MAX;容易OOM ScheduledExecutorService pool = Executors.newScheduledThreadPool(4); @Autowired StringRedisTemplate redisTemplate; @Override public void updateSkuInfo(SkuInfoUpdateVo vo) { //1、去数据库修改 //修改数据库数据的代码没写 //2、延迟双删 //2.1)、立即删 redisTemplate.delete(RedisConst.SKU_DETAIL_CACHE_PREFIX + vo.getId()); //提交延迟任务。有OOM风险 pool.schedule(()-{ redisTemplate.delete(RedisConst.SKU_DETAIL_CACHE_PREFIX+vo.getId()); },10, TimeUnit.SECONDS); } ------------------------------- 布隆过滤器 --初始化代码就是下面的代码。service-product进行初始化,因为查skuid方便 @PostConstruct public void initSkuIdBloom(){ //1、初始化布隆 RBloomFilterObject filter = redisson.getBloomFilter(RedisConst.BLOOM_SKUID); if(!filter.isExists()){ //初始化布隆 //期望插入多少数据,误判率 log.info("布隆过滤器尚未初始化,正在初始化...."); filter.tryInit(1000000,0.000001); ListLong allSkuId = getAllSkuId(); allSkuId.stream().forEach(item-{ //查询skuid,添加skuid数据到布隆.(遍历放入) filter.add(item); }); log.info("布隆过滤器初始化完成...."); } } --查询sku详情的时候的代码 RBloomFilterObject filter = redissonClient.getBloomFilter(bloomKey); //判断布隆有没有 filter.contains("xxx"); --保存sku时候的布隆代码 //把商品信息添加到布隆过滤器 RBloomFilterObject filter = redisson.getBloomFilter(RedisConst.BLOOM_SKUID); filter.add(skuId); --重置布隆过滤器。逻辑:创建新布隆,删除老布隆和老布隆的配置,修改新布隆的名字和配置为老布隆的名字和配置 @Override public void resetBloom(String bloomSkuid) { //TODO 如何重建布隆? //删了重做? // 高速换胎 //如何尽量缩短布隆不可用时间。 布隆重置期间不可用 //1、创建一个新的布隆。保存所有数据 RBloomFilterObject filter = redisson.getBloomFilter(bloomSkuid + "-new"); if (!filter.isExists()) { log.info("正在重置布隆....新布隆创建中...."); filter.tryInit(1000000, 0.000001); skuInfoService.getAllSkuId().stream().forEach(item - { filter.add(item); }); } //这步原子操作是把删除布隆和给布隆改名两步操作写成lua脚本了, //4、原子操作 (要删除的key(skuid-bloom),把更名的key(skuid-bloom-new),改为要删除的key) //KEYS[1]老布隆。KEYS[2]新布隆。 String script = "redis.call(\"del\",KEYS[1]);" + "redis.call(\"del\",\"{\"..KEYS[1]..\"}:config\");" + "redis.call(\"rename\",KEYS[2],KEYS[1]);" + "redis.call(\"rename\",\"{\"..KEYS[2]..\"