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;
}