day11谷粒商城

thymeleaf整合渲染首页

 为了看到实时效果关闭缓存

spring.thymeleaf:.cache: false

WebMVCAutoConfig自动配置

classpath是指resources,添加web包,更改controller包名为app

springmvc提供的model接口会将数据放到页面的请求域

访问首页

package com.example.gulimall.product.web;

import com.example.gulimall.product.entity.CategoryEntity;
import com.example.gulimall.product.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@Controller
public class IndexController {
    @Autowired
    CategoryService categoryService;
    @GetMapping({"/","/index.html"})
    public String indexPage(Model model){
   //TODO 查出所有1级分类
       List<CategoryEntity> categoryEntities =  categoryService.getLevelOneCategorys();
        //由视图解析器进行拼串, classpath:/template/+返回值+ .html
        // 在thymeleaf.properties中配置了默认前后缀
        model.addAttribute("categorys",categoryEntities);
        return "index";
    }
}
CategoryServiceImpl
 @Override
    public List<CategoryEntity> getLevelOneCategorys() {
        //pms_category--parent_id=0一级分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return categoryEntities;
    }

index.html

<html lang="en"  xmlns:th="http://www.thymeleaf.org">
<div th:text="${categorys}"></div>查看是否取到数据
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
         <optional>true</optional>
        </dependency>
<!--        模板引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

 ctrl+F9重新编译项目、ctrl+shift+F9重新编译当前页面

渲染二级三级分类数据

package com.example.gulimall.product.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Catelog2Vo { //2级分类vo
    private String catelogId;//父分类 1级id
    private List<Catelog3Vo> catelog3List; //三级子分类
    private String id;
    private String name;

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public static class Catelog3Vo {//3级分类vo
        private String catelog2Id;//父分类 2级id
        private String id;
        private String name;

    }
}
CategoryServiceImpl
 @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        //1.查出所有1级分类
         List<CategoryEntity> levelOneCategorys = getLevelOneCategorys();
         //2.封装数据
        Map<String, List<Catelog2Vo>> parentCid  = levelOneCategorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1.每一个的一级分类,查到这个一级分类的二级分类
            List<CategoryEntity> categoryEntities = baseMapper.selectList((new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId())));
            //封装categoryEntities结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
                //遍历当前二级分类
                catelog2Vos = categoryEntities.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    //3。找二级分类下的三级分类封装成vo
                    List<CategoryEntity> level3Category = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
                    //封装成指定的格式
                    if (level3Category != null) {
                        List<Catelog2Vo.Catelog3Vo> collect = level3Category.stream().map(l3 -> {
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatelog3List(collect);
                    }

                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
        return parentCid;
    }
IndexController
 //index/catalog.json
    @ResponseBody
    @GetMapping("/index/catalog.json")
    public Map<String,  List<Catelog2Vo>> getCategoryJson() {
        Map<String, List<Catelog2Vo>> catalogJson = categoryService.getCatalogJson();
        return catalogJson;
    }

搭建域名访问环境 

反向代理配置

 修改后再重启,

显示行号       :set number 

负载均衡到网关 

[root@localhost conf]# vi nginx.conf

 

 [root@localhost conf.d]# vi gulimall.conf

一定要放在最后一个位置 

- id: gulimall_host_route
  uri: lb://gulimall-product
  predicates:
    - Host=**.gulimall.com

 测试性能

 https://visualvm.github.io/pluginscenters.html 【找自己Jdk版本对应的】,点击下面的链接,复制第一行链接

 设置--编辑--粘贴--确定

使用xshell连接虚拟机

vi /etc/ssh/sshd_config

把密码认证yes注释去掉,no的加上注释

 vagrant--root用户名和密码登录

缓存

 springboot整合Redis

product

<!--        引入Redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
spring:
  redis:
    host: 192.168.56.10
    port: 6379

测试

@Autowired
    StringRedisTemplate redisTemplate;
    @Test
    public void testStringRedis(){
        //hello world
        ValueOperations<String,String> ops = redisTemplate.opsForValue();
        //保存
        ops.set("hello","world_"+ UUID.randomUUID().toString());
        //查询
       String hello = ops.get("hello");
        System.out.println("之前保存的数据是"+hello);
    }

加锁解决缓存击穿问题--本地锁

 //TODO 产生堆外内存溢出OutOfDirectMemoryError:
    //1)、springboot2.0以后默认使用lettuce操作redis的客户端,它使用通信
    //2)、lettuce的bug导致netty堆外内存溢出   可设置:-Dio.netty.maxDirectMemory
    //解决方案:不能直接使用-Dio.netty.maxDirectMemory去调大堆外内存
    //1)、升级lettuce客户端。      2)、切换使用jedis
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        //给缓存中放JSON字符串,拿出的JSON字符串使用需要逆转为对象类型
        //1.加入缓存逻辑,缓存中的数据是JSON字符串
        //JSON拥有可以跨语言,跨平台的兼容
        /**
         * 1、空结果缓存:解决缓存穿透问题
         * 2、设置过期时间(加随机值):解决缓存雪崩
         * 3、加锁:解决缓存击穿问题
         */
         String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if(StringUtils.isEmpty(catalogJSON)){
            //2.缓存中没有,查询数据库
            System.out.println("缓存不命中,将要查询数据库。。");
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
            //将数据库查询到的数据返回
            return catalogJsonFromDb;
        }
        System.out.println("缓存命中,直接返回。。");
      //转为指定对象
       Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
       return result;
    }
    //从数据库查询并封装分类数据

    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
        //如果缓存中有就用缓存的
//        Map<String, List<Catelog2Vo>>catalogJson = (Map<String, List<Catelog2Vo>>) cache.get("catalogJson");
        //如果缓存中有没有
//        if(cache.get("catalogJson")==null){
//          cache.put("catalogJson",parentCid);
//        }
//      return catalogJson;
        /**
         * 1.将数据库多次查询变为一次
         */
        //TODO 在分布式情况下,想要锁住所有,必须使用分布式锁
        synchronized (this) {
            //得到锁以后,应该去缓存中确定一次,如果没有才需要继续查询
            String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
            if(!StringUtils.isEmpty(catalogJSON)){
                //缓存不为null,直接返回
                //转为指定对象
                Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
                return result;
            }
            System.out.println("查询数据库了。。");
            //查询所有
            List<CategoryEntity> selectList = baseMapper.selectList(null);

            //1.查出所有1级分类
//         List<CategoryEntity> levelOneCategorys = getLevelOneCategorys();
            List<CategoryEntity> levelOneCategorys = getParent_cid(selectList, 0L);
            //2.封装数据
            Map<String, List<Catelog2Vo>> parentCid = levelOneCategorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                //1.每一个的一级分类,查到这个一级分类的二级分类
//            List<CategoryEntity> categoryEntities = baseMapper.selectList((new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId())));
                List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
                //封装categoryEntities结果
                List<Catelog2Vo> catelog2Vos = null;
                if (categoryEntities != null) {
                    //遍历当前二级分类
                    catelog2Vos = categoryEntities.stream().map(l2 -> {
                        Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                        //3。找二级分类下的三级分类封装成vo
//                    List<CategoryEntity> level3Category = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
                        List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
                        //封装成指定的格式
                        if (level3Catelog != null) {
                            List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {
                                Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                                return catelog3Vo;
                            }).collect(Collectors.toList());
                            catelog2Vo.setCatelog3List(collect);
                        }

                        return catelog2Vo;
                    }).collect(Collectors.toList());
                }
                return catelog2Vos;
            }));
            //3.数据库查询的数据并放入缓存,将对象转换为JSON并放到缓存中--一定要放在锁内
            String s = JSON.toJSONString(parentCid);
            //解决缓存雪崩1天过期
            redisTemplate.opsForValue().set("catalogJSON",s, 1,TimeUnit.DAYS);
            return parentCid;
        }
    }
    //优化
    private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList,Long parent_id) {
        List<CategoryEntity> collect = selectList.stream().filter(item ->
                item.getParentCid() == parent_id).collect(Collectors.toList());
        //return baseMapper.selectList((new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId())));
        return collect;
    }

分布式锁

 @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        //给缓存中放JSON字符串,拿出的JSON字符串使用需要逆转为对象类型
        //1.加入缓存逻辑,缓存中的数据是JSON字符串
        //JSON拥有可以跨语言,跨平台的兼容
        /**
         * 1、空结果缓存:解决缓存穿透问题
         * 2、设置过期时间(加随机值):解决缓存雪崩
         * 3、加锁:解决缓存击穿问题
         */
         String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if(StringUtils.isEmpty(catalogJSON)){
            //2.缓存中没有,查询数据库
            System.out.println("缓存不命中,将要查询数据库。。");
            //分布式锁
            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithRedisLock();
            //本地锁
//            Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbWithLocalLock();
            //将数据库查询到的数据返回
            return catalogJsonFromDb;
        }
        System.out.println("缓存命中,直接返回。。");
      //转为指定对象
       Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
       return result;
    }
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
            //1.分布式锁,去Redis占坑
//         Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "1111");//看做是Redis命令的SETNX
        //过期时间和加锁必须同步
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);

        if(lock){
            System.out.println("获取分布式锁成功。。");
            //加锁成功...执行业务
            /**
             * 为了防止加锁后出现异常,导致死锁问题
             * 一直不能删除锁,别的线程也无法占到锁,所以设置过期时间,30s后自动删
             * 注意设置过期时间和加锁必须同步,保持在一个原子的
             */
//            redisTemplate.expire("lock",30,TimeUnit.SECONDS);


            /**
             * 业务超时完成,但是锁已经被删除,另一个线程占锁进来,也超时完成,另一个线程也占锁。。。
             * 1.业务超时
             * 解决:删除锁必须保证原子性,使用Redis+lua脚本完成
             * 2.删除锁是否删的是自己的锁
             * 解决:占锁的时候,值指定UUID,每个线程匹配机制的锁才能删除
             */
            Map<String, List<Catelog2Vo>> dataFromDb;
            try{
             dataFromDb = getDataFromDb();
            }finally {
             //获取值+对比成功删除=原子操作
             //Lua脚本完成--解锁
//             //调用脚本删除锁,返回0或1;
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),Collections.singletonList("lock"), uuid);
                System.out.println("删除锁成功了。。");
            }
            return dataFromDb;
//            System.out.println("加锁成功");
        }else {
            System.out.println("获取分布式锁不成功。。等待重试");
           try{
               Thread.sleep(200);
           }catch (Exception e){}
            //加锁失败...重试
            //休眠100Ms重试
            return getCatalogJsonFromDbWithRedisLock();//【自旋方式】
        }
       //得到锁以后,应该去缓存中确定一次,如果没有才需要继续查询


    }
//    @Transactional
    private Map<String, List<Catelog2Vo>> getDataFromDb() {
        String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
        if (!StringUtils.isEmpty(catalogJSON)) {
            //缓存不为null,直接返回
            //转为指定对象
            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return result;
        }
        System.out.println("查询数据库了。。");
        //查询所有
        List<CategoryEntity> selectList = baseMapper.selectList(null);

        //1.查出所有1级分类
//         List<CategoryEntity> levelOneCategorys = getLevelOneCategorys();
        List<CategoryEntity> levelOneCategorys = getParent_cid(selectList, 0L);
        //2.封装数据
        Map<String, List<Catelog2Vo>> parentCid = levelOneCategorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1.每一个的一级分类,查到这个一级分类的二级分类
//            List<CategoryEntity> categoryEntities = baseMapper.selectList((new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId())));
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
            //封装categoryEntities结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
                //遍历当前二级分类
                catelog2Vos = categoryEntities.stream().map(l2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    //3。找二级分类下的三级分类封装成vo
//                    List<CategoryEntity> level3Category = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
                    List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
                    //封装成指定的格式
                    if (level3Catelog != null) {
                        List<Catelog2Vo.Catelog3Vo> collect = level3Catelog.stream().map(l3 -> {
                            Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatelog3List(collect);
                    }

                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
        //3.数据库查询的数据并放入缓存,将对象转换为JSON并放到缓存中--一定要放在锁内
        String s = JSON.toJSONString(parentCid);
        //解决缓存雪崩1天过期
        redisTemplate.opsForValue().set("catalogJSON", s, 1, TimeUnit.DAYS);
        return parentCid;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值